J'ai un service AngularJS que je veux initialiser avec des données asynchrones. Quelque chose comme ça:
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();
}
};
});
Évidemment, cela ne fonctionnera pas parce que si quelque chose essaie d'appeler doStuff()
avant myData
revient, j'obtiendrai une exception de pointeur nul. Autant que je sache en lisant certaines des autres questions posées ici et ici, j'ai quelques options, mais aucune ne semble très propre (peut-être que je manque quelque chose):
Service d'installation avec "exécuter"
Lors de la configuration de mon application, procédez comme suit:
myApp.run(function ($http, MyService) {
$http.get('data.json').success(function (data) {
MyService.setData(data);
});
});
Ensuite, mon service ressemblerait à ceci:
myModule.service('MyService', function() {
var myData = null;
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Cela fonctionne parfois, mais si les données asynchrones prennent plus de temps qu'il n'en faut pour que tout soit initialisé, j'obtiens une exception de pointeur nul lorsque j'appelle doStuff()
Utilisez des objets de promesse
Cela fonctionnerait probablement. Le seul inconvénient partout où j'appelle MyService, je devrai savoir que doStuff () renvoie une promesse et tout le code devra nous then
interagir avec la promesse. Je préfère simplement attendre que mes données soient de retour avant de charger mon application.
Bootstrap manuel
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 Je pourrais envoyer mon JSON directement à une variable Javascript globale:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
Il serait alors disponible lors de l'initialisation MyService
:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
Cela fonctionnerait aussi, mais j'ai une variable javascript globale qui sent mauvais.
Ce sont mes seules options? L'une de ces options est-elle meilleure que les autres? Je sais que c'est une question assez longue, mais je voulais montrer que j'ai essayé d'explorer toutes mes options. Tout conseil serait grandement apprécié.
la source
$http
, puis enregistre les données dans un service, puis amorce une application.Réponses:
Avez-vous regardé
$routeProvider.when('/path',{ resolve:{...}
? Cela peut rendre l'approche de la promesse un peu plus propre:Exposez une promesse à votre service:
Ajoutez
resolve
à votre configuration d'itinéraire:Votre contrôleur ne sera pas instancié avant que toutes les dépendances ne soient résolues:
J'ai fait un exemple sur plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
la source
resolve
propriété à un contrôleur qui n'est pas mentionné dans$routeProvider
. Par exemple<div ng-controller="IndexCtrl"></div>
,. Ici, le contrôleur est explicitement mentionné et n'est pas chargé via le routage. Dans un tel cas, comment retarderait-on alors l'instanciation du contrôleur?Basé sur la solution de Martin Atkins, voici une solution pure angulaire complète et concise:
Cette solution utilise une fonction anonyme auto-exécutable pour obtenir le service $ http, demander la configuration et l'injecter dans une constante appelée CONFIG lorsqu'elle sera disponible.
Une fois complètement, nous attendons que le document soit prêt, puis amorçons l'application Angular.
Il s'agit d'une légère amélioration par rapport à la solution de Martin, qui a reporté la récupération de la configuration jusqu'à ce que le document soit prêt. Pour autant que je sache, il n'y a aucune raison de retarder l'appel $ http pour cela.
Tests unitaires
Remarque: J'ai découvert que cette solution ne fonctionne pas bien lors des tests unitaires lorsque le code est inclus dans votre
app.js
fichier. La raison en est que le code ci-dessus s'exécute immédiatement lorsque le fichier JS est chargé. Cela signifie que le framework de test (Jasmine dans mon cas) n'a pas la possibilité de fournir une implémentation simulée de$http
.Ma solution, dont je ne suis pas entièrement satisfait, a été de déplacer ce code dans notre
index.html
fichier, de sorte que l'infrastructure de test unitaire Grunt / Karma / Jasmine ne le voit pas.la source
J'ai utilisé une approche similaire à celle décrite par @XMLilley mais je voulais avoir la possibilité d'utiliser les services AngularJS comme
$http
pour charger la configuration et faire une initialisation supplémentaire sans utiliser d'API de bas niveau ou jQuery.L'utilisation
resolve
sur les routes n'était pas non plus une option car j'avais besoin que les valeurs soient disponibles sous forme de constantes au démarrage de mon application, même enmodule.config()
blocs.J'ai créé une petite application AngularJS qui charge la configuration, les définit comme constantes sur l'application réelle et l'amorce.
Voyez-le en action (en utilisant
$timeout
au lieu de$http
) ici: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewMISE À JOUR
Je recommanderais d'utiliser l'approche décrite ci-dessous par Martin Atkins et JBCP.
MISE À JOUR 2
Parce que j'en avais besoin dans plusieurs projets, je viens de publier un module bower qui s'en occupe: https://github.com/philippd/angular-deferred-bootstrap
Exemple qui charge des données depuis le back-end et définit une constante appelée APP_CONFIG sur le module AngularJS:
la source
Le cas «bootstrap manuel» peut accéder aux services angulaires en créant manuellement un injecteur avant le bootstrap. Cet injecteur initial sera autonome (ne sera attaché à aucun élément) et n'inclura qu'un sous-ensemble des modules chargés. Si vous n'avez besoin que des services Angular de base, il suffit de charger
ng
, comme ceci:Vous pouvez, par exemple, utiliser le
module.constant
mécanisme pour rendre les données disponibles pour votre application:Celui-ci
myAppConfig
peut désormais être injecté comme tout autre service, et en particulier il est disponible lors de la phase de configuration:ou, pour une application plus petite, vous pouvez simplement injecter la configuration globale directement dans votre service, au détriment de la diffusion des connaissances sur le format de configuration dans l'application.
Bien sûr, puisque les opérations asynchrones ici bloqueront le bootstrap de l'application, et donc bloqueront la compilation / liaison du modèle, il est sage d'utiliser la
ng-cloak
directive pour empêcher le modèle non analysé de s'afficher pendant le travail. Vous pouvez également fournir une sorte d'indication de chargement dans le DOM, en fournissant du code HTML qui s'affiche uniquement jusqu'à ce que AngularJS s'initialise:J'ai créé un exemple complet et fonctionnel de cette approche sur Plunker, en chargeant la configuration à partir d'un fichier JSON statique comme exemple.
la source
J'ai eu le même problème: j'aime l'
resolve
objet, mais cela ne fonctionne que pour le contenu de ng-view. Et si vous avez des contrôleurs (pour la navigation de niveau supérieur, disons) qui existent en dehors de ng-view et qui doivent être initialisés avec des données avant même que le routage ne commence? Comment éviter de tourner autour du serveur juste pour que cela fonctionne?Utilisez un bootstrap manuel et une constante angulaire . Un XHR naïf vous récupère vos données, et vous amorcez angulairement dans son rappel, qui traite de vos problèmes asynchrones. Dans l'exemple ci-dessous, vous n'avez même pas besoin de créer une variable globale. Les données renvoyées n'existent que dans une portée angulaire en tant qu'injectable et ne sont même pas présentes à l'intérieur des contrôleurs, des services, etc., sauf si vous les injectez. (Tout comme vous injecteriez la sortie de votre
resolve
objet dans le contrôleur pour une vue routée.) Si vous préférez ensuite interagir avec ces données en tant que service, vous pouvez créer un service, injecter les données, et personne ne sera jamais plus sage .Exemple:
Maintenant, votre
NavData
constante existe. Allez-y et injectez-le dans un contrôleur ou un service:Bien sûr, l'utilisation d'un objet XHR nu supprime un certain nombre de subtilités qui
$http
ou JQuery prendrait soin de vous, mais cet exemple fonctionne sans dépendances spéciales, du moins pour un simpleget
. Si vous voulez un peu plus de puissance pour votre demande, chargez une bibliothèque externe pour vous aider. Mais je ne pense pas qu'il soit possible d'accéder aux$http
outils angulaires ou autres dans ce contexte.( Poste lié à SO )
la source
Ce que vous pouvez faire dans votre .config pour l'application est de créer l'objet de résolution pour l'itinéraire et dans la fonction, passez dans $ q (objet de promesse) et le nom du service dont vous dépendez, et résolvez la promesse dans le fonction de rappel pour le $ http dans le service comme ceci:
CONFIGURATION D'ITINÉRAIRE
Angular ne rendra pas le modèle ou rendra le contrôleur disponible jusqu'à ce que defer.resolve () ait été appelé. Nous pouvons le faire à notre service:
UN SERVICE
Maintenant que MyService a les données affectées à sa propriété de données et que la promesse dans l'objet de résolution d'itinéraire a été résolue, notre contrôleur pour l'itinéraire prend vie, et nous pouvons affecter les données du service à notre objet contrôleur.
MANETTE
Désormais, toutes nos liaisons dans le cadre du contrôleur pourront utiliser les données provenant de MyService.
la source
resolve
un objet avec une propriété comme fonction. il a donc fini par êtreresolve:{ dataFetch: function(){ // call function here } }
J'ai donc trouvé une solution. J'ai créé un service angularJS, nous l'appellerons MyDataRepository et j'ai créé un module pour celui-ci. Je sers ensuite ce fichier javascript depuis mon contrôleur côté serveur:
HTML:
Du côté serveur:
Je peux ensuite injecter MyDataRepository partout où j'en ai besoin:
Cela a très bien fonctionné pour moi, mais je suis ouvert à toute rétroaction si quelqu'un en a. }
la source
En outre, vous pouvez utiliser les techniques suivantes pour provisionner votre service globalement, avant l'exécution des contrôleurs réels: https://stackoverflow.com/a/27050497/1056679 . Il suffit de résoudre vos données globalement puis de les transmettre à votre service en
run
bloc par exemple.la source
Vous pouvez utiliser
JSONP
pour charger de manière asynchrone des données de service. La demande JSONP sera effectuée lors du chargement initial de la page et les résultats seront disponibles avant le démarrage de votre application. De cette façon, vous n'aurez pas à gonfler votre routage avec des résolutions redondantes.Votre html ressemblerait à ceci:
la source
Le moyen le plus simple de récupérer n'importe quel répertoire initialize utilise ng-init.
Mettez simplement la portée div ng-init où vous voulez récupérer les données init
index.html
index.js
la source