Ich habe einen AngularJS-Dienst, den ich mit asynchronen Daten initialisieren möchte. Etwas wie das:
myModule.service('MyService', function($http) {
var myData = null;
$http.get('data.json').success(function (data) {
myData = data;
});
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Offensichtlich funktioniert dies nicht, denn wenn etwas versucht aufzurufen, doStuff()
bevor es myData
zurückkommt, erhalte ich eine Nullzeigerausnahme. Soweit ich aus den anderen hier und hier gestellten Fragen ersehen kann , habe ich einige Optionen, aber keine davon scheint sehr sauber zu sein (vielleicht fehlt mir etwas):
Setup Service mit "run"
Gehen Sie beim Einrichten meiner App folgendermaßen vor:
myApp.run(function ($http, MyService) {
$http.get('data.json').success(function (data) {
MyService.setData(data);
});
});
Dann würde mein Service so aussehen:
myModule.service('MyService', function() {
var myData = null;
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Dies funktioniert manchmal, aber wenn die asynchronen Daten länger dauern als es dauert, bis alles initialisiert ist, erhalte ich beim Aufruf eine Nullzeigerausnahme doStuff()
Verwenden Sie Versprechen Objekte
Das würde wahrscheinlich funktionieren. Der einzige Nachteil überall, wo ich MyService anrufe, ist, dass doStuff () ein Versprechen zurückgibt und der gesamte Code für die then
Interaktion mit dem Versprechen erforderlich ist . Ich würde lieber warten, bis myData zurück ist, bevor ich meine Anwendung lade.
Manueller Bootstrap
angular.element(document).ready(function() {
$.getJSON("data.json", function (data) {
// can't initialize the data here because the service doesn't exist yet
angular.bootstrap(document);
// too late to initialize here because something may have already
// tried to call doStuff() and would have got a null pointer exception
});
});
Global Javascript Var Ich könnte meinen JSON direkt an eine globale Javascript-Variable senden:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
Dann wäre es beim Initialisieren verfügbar MyService
:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
Das würde auch funktionieren, aber dann habe ich eine globale Javascript-Variable, die schlecht riecht.
Sind das meine einzigen Möglichkeiten? Ist eine dieser Optionen besser als die anderen? Ich weiß, dass dies eine ziemlich lange Frage ist, aber ich wollte zeigen, dass ich versucht habe, alle meine Optionen zu erkunden. Jede Anleitung wäre sehr dankbar.
quelle
$http
abzurufen, Daten in einem Dienst zu speichern und dann eine App zu booten.Antworten:
Hast du dir das angeschaut
$routeProvider.when('/path',{ resolve:{...}
? Es kann den Versprechensansatz ein bisschen sauberer machen:Stellen Sie ein Versprechen in Ihren Dienst:
Fügen Sie
resolve
Ihrer Routenkonfiguration Folgendes hinzu:Ihr Controller wird erst instanziiert, wenn alle Abhängigkeiten aufgelöst sind:
Ich habe ein Beispiel bei plnkr gemacht: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
quelle
resolve
Eigenschaft einem Controller zuweisen, der in nicht erwähnt wird$routeProvider
. Zum Beispiel<div ng-controller="IndexCtrl"></div>
. Hier wird der Controller explizit erwähnt und nicht über das Routing geladen. Wie würde man dann in einem solchen Fall die Instanziierung des Controllers verzögern?Basierend auf der Lösung von Martin Atkins finden Sie hier eine vollständige, präzise Lösung für den reinen Winkel:
Diese Lösung verwendet eine selbstausführende anonyme Funktion, um den $ http-Dienst abzurufen, die Konfiguration anzufordern und sie in eine Konstante namens CONFIG einzufügen, sobald sie verfügbar ist.
Sobald dies vollständig ist, warten wir, bis das Dokument fertig ist, und booten dann die Angular-App.
Dies ist eine leichte Verbesserung gegenüber Martins Lösung, bei der das Abrufen der Konfiguration erst nach Fertigstellung des Dokuments verschoben wurde. Soweit ich weiß, gibt es keinen Grund, den $ http-Aufruf dafür zu verzögern.
Unit Testing
Hinweis: Ich habe festgestellt, dass diese Lösung beim Unit-Test nicht gut funktioniert, wenn der Code in Ihrer
app.js
Datei enthalten ist. Der Grund dafür ist, dass der obige Code sofort ausgeführt wird, wenn die JS-Datei geladen wird. Dies bedeutet, dass das Test-Framework (in meinem Fall Jasmine) keine Chance hat, eine Scheinimplementierung von bereitzustellen$http
.Meine Lösung, mit der ich nicht ganz zufrieden bin, bestand darin, diesen Code in unsere
index.html
Datei zu verschieben, sodass die Grunt / Karma / Jasmine-Unit-Test-Infrastruktur ihn nicht sieht.quelle
Ich habe einen ähnlichen Ansatz wie den von @XMLilley beschriebenen verwendet, wollte aber die Möglichkeit haben, AngularJS-Dienste
$http
zu verwenden, um die Konfiguration zu laden und eine weitere Initialisierung ohne Verwendung von APIs auf niedriger Ebene oder jQuery durchzuführen.Die Verwendung
resolve
auf Routen war ebenfalls keine Option, da die Werte beim Starten meiner App auch inmodule.config()
Blöcken als Konstanten verfügbar sein mussten .Ich habe eine kleine AngularJS-App erstellt, die die Konfiguration lädt, sie als Konstanten für die eigentliche App festlegt und sie bootet.
Sehen Sie es in Aktion (
$timeout
anstelle von$http
) hier: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewAKTUALISIEREN
Ich würde empfehlen, den unten von Martin Atkins und JBCP beschriebenen Ansatz zu verwenden.
UPDATE 2
Da ich es in mehreren Projekten brauchte, habe ich gerade ein Bower-Modul veröffentlicht, das sich darum kümmert: https://github.com/philippd/angular-deferred-bootstrap
Beispiel, das Daten aus dem Back-End lädt und eine Konstante namens APP_CONFIG im AngularJS-Modul festlegt:
quelle
Der Fall "Manueller Bootstrap" kann Zugriff auf Angular-Dienste erhalten, indem vor dem Bootstrap manuell ein Injektor erstellt wird. Dieser anfängliche Injektor ist eigenständig (nicht an Elemente angeschlossen) und enthält nur eine Teilmenge der geladenen Module. Wenn Sie nur Angular-Kerndienste benötigen, können Sie
ng
diese wie folgt laden :Sie können beispielsweise den
module.constant
Mechanismus verwenden, um Daten für Ihre App verfügbar zu machen:Dies
myAppConfig
kann jetzt wie jeder andere Dienst injiziert werden und ist insbesondere während der Konfigurationsphase verfügbar:Bei einer kleineren App können Sie die globale Konfiguration auch direkt in Ihren Dienst einfügen, um das Wissen über das Konfigurationsformat in der gesamten Anwendung zu verbreiten.
Da die asynchronen Vorgänge hier den Bootstrap der Anwendung und damit das Kompilieren / Verknüpfen der Vorlage blockieren, ist es
ng-cloak
natürlich ratsam, die Anweisung zu verwenden, um zu verhindern, dass die nicht analysierte Vorlage während der Arbeit angezeigt wird. Sie können auch eine Art Ladeanzeige im DOM bereitstellen, indem Sie HTML-Code bereitstellen, der nur angezeigt wird, bis AngularJS initialisiert wird:Ich habe ein vollständiges, funktionierendes Beispiel für diesen Ansatz in Plunker erstellt und die Konfiguration als Beispiel aus einer statischen JSON-Datei geladen.
quelle
Ich hatte das gleiche Problem: Ich liebe das
resolve
Objekt, aber das funktioniert nur für den Inhalt von ng-view. Was ist, wenn Sie Controller (z. B. für die Navigation auf oberster Ebene) haben, die außerhalb von ng-view existieren und mit Daten initialisiert werden müssen, bevor das Routing überhaupt beginnt? Wie vermeiden wir es, auf der Serverseite herumzuspielen, damit das funktioniert?Verwenden Sie einen manuellen Bootstrap und eine Winkelkonstante . Ein naiive XHR liefert Ihnen Ihre Daten und Sie booten in seinem Rückruf eckig, der sich mit Ihren asynchronen Problemen befasst. Im folgenden Beispiel müssen Sie nicht einmal eine globale Variable erstellen. Die zurückgegebenen Daten existieren nur im Winkelbereich als injizierbare Daten und sind nicht einmal in Controllern, Diensten usw. vorhanden, es sei denn, Sie injizieren sie. (Ähnlich wie Sie die Ausgabe Ihres
resolve
Objekts für eine geroutete Ansicht in den Controller einspeisen würden.) Wenn Sie danach lieber mit diesen Daten als Dienst interagieren möchten, können Sie einen Dienst erstellen, die Daten einfügen, und niemand wird jemals klüger sein .Beispiel:
Jetzt existiert deine
NavData
Konstante. Fahren Sie fort und injizieren Sie es in eine Steuerung oder einen Dienst:Wenn Sie ein nacktes XHR-Objekt verwenden, werden natürlich einige der Feinheiten entfernt, die
$http
JQuery für Sie erledigen würde, aber dieses Beispiel funktioniert ohne besondere Abhängigkeiten, zumindest für einfacheget
. Wenn Sie etwas mehr Leistung für Ihre Anfrage benötigen, laden Sie eine externe Bibliothek, um Ihnen zu helfen. Aber ich denke nicht, dass es$http
in diesem Zusammenhang möglich ist, auf Winkel oder andere Werkzeuge zuzugreifen .(SO verwandter Beitrag )
quelle
In Ihrer .config für die App können Sie das Auflösungsobjekt für die Route erstellen und in der Funktion $ q (Versprechenobjekt) und den Namen des Dienstes übergeben, von dem Sie abhängig sind, und das Versprechen in der Rückruffunktion für das $ http im Dienst wie folgt:
ROUTENKONFIG
Angular rendert die Vorlage nicht und stellt den Controller erst zur Verfügung, wenn defer.resolve () aufgerufen wurde. Das können wir in unserem Service tun:
BEDIENUNG
Nachdem MyService die Daten seiner Dateneigenschaft zugewiesen hat und das Versprechen im Routenauflösungsobjekt aufgelöst wurde, wird unser Controller für die Route aktiviert und wir können die Daten aus dem Service unserem Controllerobjekt zuweisen.
REGLER
Jetzt können alle unsere Bindungen im Rahmen des Controllers die Daten verwenden, die von MyService stammen.
quelle
resolve
ein Objekt mit einer Eigenschaft als Funktion erstellen musste. so endete esresolve:{ dataFetch: function(){ // call function here } }
Also habe ich eine Lösung gefunden. Ich habe einen angleJS-Dienst erstellt, wir nennen ihn MyDataRepository, und ich habe ein Modul dafür erstellt. Ich stelle dann diese Javascript-Datei von meinem serverseitigen Controller bereit:
HTML:
Serverseitig:
Ich kann MyDataRepository dann injizieren, wo immer ich es brauche:
Das hat bei mir sehr gut funktioniert, aber ich bin offen für Rückmeldungen, wenn jemand welche hat. }}
quelle
Sie können die folgenden Techniken auch verwenden, um Ihren Dienst global bereitzustellen, bevor die tatsächlichen Controller ausgeführt werden: https://stackoverflow.com/a/27050497/1056679 . Lösen Sie Ihre Daten einfach global auf und übergeben Sie sie
run
beispielsweise blockweise an Ihren Dienst .quelle
Sie können
JSONP
damit Dienstdaten asynchron laden. Die JSONP-Anforderung wird beim ersten Laden der Seite gestellt und die Ergebnisse sind verfügbar, bevor Ihre Anwendung gestartet wird. Auf diese Weise müssen Sie Ihr Routing nicht mit redundanten Auflösungen aufblähen.Ihr HTML würde so aussehen:
quelle
Der einfachste Weg, eine Initialisierung abzurufen, ist das ng-init-Verzeichnis.
Platzieren Sie einfach den Bereich ng-init div dort, wo Sie Init-Daten abrufen möchten
index.html
index.js
quelle