2013-08-22 9 views
9

Ich versuche, eine einzige $http Anfrage, um eine meiner JSON-Dateien zu erhalten und die Daten über alle meine Controller zu verwenden.Share async Daten zwischen Controllern ohne mehrere Anfragen

Ich habe auf egghead.io gesehen, wie man Daten über mehrere Controller verteilt, und ich habe auch diese StackOverflow Frage gelesen: "Sharing a variable between controllers in angular.js".

Die Antworten dort verwenden jedoch nicht das Modul $http. Wenn Sie $http verwenden, verfügen die Controller nicht über die Daten, an denen sie arbeiten müssen. Zum Zeitpunkt des Empfangs der Antwort ist es bereits zu spät.

Ich fand dann die Methode $q.defer und diese Frage auf Stackoverflow: „AngularJS share asynchronous service data between controllers

Die Lösung gepostet es funktioniert gut, aber es hat zwei Probleme:

  1. Jeder Controller löst die $http Anfrage die gleichen Daten erhalten, die bereits in einem anderen Controller verwendet wurden; und,
  2. Wenn ich versuche, die empfangenen Daten zu manipulieren, habe ich eine then Funktion.

Unten können Sie meinen Code sehen:

controllers.js

'use strict'; 

/* Controllers */ 

function appInstallerListCtrl($scope, Data) { 
    $scope.apps = Data; 
} 

function appInstallerDetailCtrl($scope, $routeParams, Data) { 
    $scope.appId = $routeParams.appId; 
    $scope.apps = Data; 
    console.log($scope.apps); // <-- then function 
    console.log(Data); // <-- then function with $vv data returned but I can't access it 

    for (var i in $scope.apps) // <--- no way, baby! 
    console.log(i); 
} 

app.js

var app = angular.module('appInstaller', []); 

app.factory('Data', function($http, $q) { 
    var defer = $q.defer(); 
    $http.get('apps.json').then(function(result) { 
    defer.resolve(result.data.versions.version); 
    }); 
    return defer.promise; 
}); 

app.config(['$routeProvider', function($routeProvider) { 
    $routeProvider. 
    when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}). 
    when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}). 
    otherwise({redirectTo: '/app'}); 
}]); 

Was ich haben möchte, ist, dass Beim Start der App wird die $http Anfrage p sein Die Antwort wird in der gesamten App auf allen Controllern verwendet.

Dank

Antwort

1

Da Sie ein Versprechen verwenden, um die Daten von Versprechen verwenden Sie die Callback-Syntax

function appInstallerDetailCtrl($scope, $routeParams, Data) { 
    $scope.appId = $routeParams.appId; 
    Data.then(function(returnedData) { 
     $scope.apps=returnedData; 
     console.log($scope.apps); 
     for (var i in $scope.apps) 
      console.log(i) 
    }); 
} 

Stellen Sie sicher, diese

defer.resolve(result.data.versions.version); 

Entschlossenheit kehrt Array, für das zurück zugreifen über dem Code zu arbeiten. Oder sehen Sie, was in den Daten vorhanden ist und justieren Sie den Controller-Code.

+0

Danke, aber der Code ist nicht richtig. Ich erhalte einen Fehler bei: Uncaught SyntaxError: Unerwartetes Token {nach der Data.then und es gibt immer noch die) Klammer. – kiwi1342

+0

Siehe die Dokumentation auf $ q hier http://docs.angularjs.org/api/ng.$q, ich habe gerade die Grundidee hervorgehoben. Ich habe versucht, meinen Code auch zu reparieren. – Chandermani

+0

wow, es funktioniert! vielen Dank und ich werde das Dokument, das Sie markiert haben, überprüfen. – kiwi1342

14

Ich mag es, meine Daten in den Dienst zu speichern und ein Versprechen an die Controller zurückgeben, denn normalerweise müssen Sie dort mit Fehlern umgehen.

app.factory('Data', function($http, $q) { 
    var data = [], 
     lastRequestFailed = true, 
     promise; 
    return { 
     getApps: function() { 
     if(!promise || lastRequestFailed) { 
      // $http returns a promise, so we don't need to create one with $q 
      promise = $http.get('apps.json') 
      .then(function(res) { 
       lastRequestFailed = false; 
       data = res.data; 
       return data; 
      }, function(res) { 
       return $q.reject(res); 
      }); 
     } 
     return promise; 
     } 
    } 
}); 

.controller('appInstallerListCtrl', ['$scope','Data', 
function($scope, Data) { 
    Data.getApps() 
    .then(function(data) { 
     $scope.data = data; 
    }, function(res) { 
     if(res.status === 500) { 
      // server error, alert user somehow 
     } else { 
      // probably deal with these errors differently 
     } 
    }); 
}]); 

Alle Rückrufe, die registriert werden, nachdem ein Versprechen sofort mit dem gleichen Ergebnis/FAILURE_REASON abgelehnt aufgelöst/aufgelöst/abgelehnt wurde, werden. Einmal aufgelöst/abgelehnt, kann eine Zusage nicht geändert werden (ihr Status). So wird der erste Controller, der getApps() aufrufen wird, das Versprechen erstellen. Alle anderen Controller, die getApps() aufrufen, erhalten sofort das Versprechen zurückgegeben.

+0

+1, dies ist (denke ich) die beste Praxis für die Synchronisierung großer App. Vielen Dank, und so oft auf stackoverflow, Ihr Recht :) –

+1

Bindung an Versprechen funktioniert nicht in den neueren Versionen (1.2+ denke ich). Benutze Data.getApps(). Then (function (result) {// speichere das Ergebnis hier in $ scope}) – shrutyzet

+0

@shrutyzet, thanks. Endlich habe ich meine Antwort aktualisiert. –

1

Ich fand den Weg nicht sicher, Wetter es ist ein bester Ansatz, es zu tun oder nicht.

In HTML

<body ng-app="myApp"> 
    <div ng-controller="ctrl">{{user.title}}</div> 
    <hr> 
    <div ng-controller="ctrl2">{{user.title}}</div> 
</body> 

In Javascript

var app = angular.module('myApp', []); 
    app.controller('ctrl', function($scope, $http, userService) { 
     userService.getUser().then(function(user) { 
     $scope.user = user; 
     }); 
    }); 

    app.controller('ctrl2', function($scope, $http, userService) { 
     userService.getUser().then(function(user) { 
     $scope.user = user; 
     }); 
    }); 

    app.factory('userService', function($http, $q) { 
    var promise; 
    var deferred = $q.defer(); 
     return { 
     getUser: function() { 
      if(!promise){  
      promise = $http({ 
       method: "GET", 
       url: "https://jsonplaceholder.typicode.com/posts/1" 
      }).success(function(res) { 
       data = res.data; 
       deferred.resolve(res); 
      }) 
      .error(function(err, status) { 
       deferred.reject(err) 
      }); 
      return deferred.promise; 
      } 
      return deferred.promise; 
     } 
     } 
    }); 

Dies wird genau nur 1 HTTP-Anforderung machen.

0

Mein Problem war, dass ich nicht warten wollte resolve vor dem Laden eines anderen Controllers, weil es eine "Verzögerung" zwischen den Controllern zeigen würde, wenn das Netzwerk langsam ist. Meine Arbeitslösung Hindurchleiten eines Versprechen zwischen Steuerungen über ui-router ‚s params und die Daten von versprechen kann asynchron in der zweiten Steuerung als solche geladen werden:

app.route.js - Einstellung der verfügbaren params zu übergeben werden SearchController, die die Suche zeigt Ergebnisse

 .state('search', { 
      url: '/search', 
      templateUrl: baseDir + 'search/templates/index.html', 
      controller: 'SearchController', 
      params: { 
       searchPromise: null 
      } 
     }) 

landing.controller.js - Controller, wo der Benutzer Sucheingabe fügt hinzu und legt

let promise = SearchService.search(form); 
    $state.go('search', { 
     searchPromise: promise 
    }); 

search.service.js - ein Service, der

function search(params) { 
     return new Promise(function (resolve, reject) { 
      $timeout(function() { 
       resolve([]) // mimic a slow query but illustrates a point 
      }, 3000) 
     }) 
    } 

search.controller.js ein Versprechen von der Benutzereingabe zurück - wo Suchsteuerung

let promise = $state.params.searchPromise; 

    promise.then(r => { 
     console.log('search result',r); 
    })