Tengo un servicio AngularJS que quiero inicializar con algunos datos asincrónicos. Algo como esto:
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();
}
};
});
Obviamente, esto no funcionará porque si algo intenta llamar doStuff()
antes de que myData
vuelva, obtendré una excepción de puntero nulo. Por lo que puedo decir al leer algunas de las otras preguntas formuladas aquí y aquí , tengo algunas opciones, pero ninguna de ellas parece muy limpia (tal vez me falta algo):
Servicio de configuración con "ejecutar"
Al configurar mi aplicación, haga esto:
myApp.run(function ($http, MyService) {
$http.get('data.json').success(function (data) {
MyService.setData(data);
});
});
Entonces mi servicio se vería así:
myModule.service('MyService', function() {
var myData = null;
return {
setData: function (data) {
myData = data;
},
doStuff: function () {
return myData.getSomeData();
}
};
});
Esto funciona algunas veces, pero si los datos asíncronos tardan más de lo que se tarda en inicializar todo, obtengo una excepción de puntero nulo cuando llamo doStuff()
Usa objetos de promesa
Esto probablemente funcionaría. El único inconveniente en todas partes que llamo MyService tendrá que saber que doStuff () devuelve una promesa y todo el código nos tendrá then
que interactuar con la promesa. Prefiero esperar hasta que myData regrese antes de cargar mi aplicación.
Bootstrap manual
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 Podría enviar mi JSON directamente a una variable global de Javascript:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
Entonces estaría disponible al inicializar MyService
:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
Esto también funcionaría, pero luego tengo una variable global de JavaScript que huele mal.
¿Son estas mis únicas opciones? ¿Una de estas opciones es mejor que las otras? Sé que esta es una pregunta bastante larga, pero quería mostrar que he tratado de explorar todas mis opciones. Cualquier orientación sería muy apreciada.
fuente
$http
, luego guardar datos en un servicio y luego arrancar una aplicación.Respuestas:
¿Has echado un vistazo
$routeProvider.when('/path',{ resolve:{...}
? Puede hacer que el enfoque de la promesa sea un poco más limpio:Exponer una promesa en su servicio:
Agregue
resolve
a su configuración de ruta:Su controlador no se instanciará antes de que se resuelvan todas las dependencias:
He hecho un ejemplo en plnkr: http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
fuente
resolve
propiedad a un controlador que no se menciona en$routeProvider
? Por ejemplo,<div ng-controller="IndexCtrl"></div>
. Aquí, el controlador se menciona explícitamente y no se carga a través del enrutamiento. En tal caso, ¿cómo se retrasaría la creación de instancias del controlador?Basado en la solución de Martin Atkins, aquí hay una solución completa, concisa, puramente angular:
Esta solución utiliza una función anónima de ejecución automática para obtener el servicio $ http, solicitar la configuración e inyectarlo en una constante llamada CONFIG cuando esté disponible.
Una vez completamente, esperamos hasta que el documento esté listo y luego arranquemos la aplicación Angular.
Esta es una ligera mejora sobre la solución de Martin, que aplazó la obtención de la configuración hasta que el documento esté listo. Hasta donde yo sé, no hay razón para retrasar la llamada $ http para eso.
Examen de la unidad
Nota: He descubierto que esta solución no funciona bien cuando se realizan pruebas unitarias cuando el código está incluido en su
app.js
archivo. La razón de esto es que el código anterior se ejecuta inmediatamente cuando se carga el archivo JS. Esto significa que el marco de prueba (Jasmine en mi caso) no tiene la oportunidad de proporcionar una implementación simulada de$http
.Mi solución, con la que no estoy completamente satisfecho, fue mover este código a nuestro
index.html
archivo, para que la infraestructura de prueba de la unidad Grunt / Karma / Jasmine no lo vea.fuente
Utilicé un enfoque similar al descrito por @XMLilley, pero quería tener la capacidad de usar los servicios de AngularJS, como
$http
cargar la configuración y realizar una inicialización adicional sin el uso de API de bajo nivel o jQuery.El uso
resolve
en rutas tampoco era una opción porque necesitaba que los valores estuvieran disponibles como constantes cuando se inicia mi aplicación, incluso enmodule.config()
bloques.Creé una pequeña aplicación AngularJS que carga la configuración, las establece como constantes en la aplicación real y la inicia.
Véalo en acción (usando en
$timeout
lugar de$http
) aquí: http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewACTUALIZAR
Recomendaría utilizar el enfoque descrito a continuación por Martin Atkins y JBCP.
ACTUALIZACIÓN 2
Debido a que lo necesitaba en múltiples proyectos, acabo de lanzar un módulo Bower que se encarga de esto: https://github.com/philippd/angular-deferred-bootstrap
Ejemplo que carga datos del back-end y establece una constante llamada APP_CONFIG en el módulo AngularJS:
fuente
El caso "bootstrap manual" puede obtener acceso a servicios angulares creando manualmente un inyector antes del bootstrap. Este inyector inicial estará solo (no se adjuntará a ningún elemento) e incluirá solo un subconjunto de los módulos que se cargan. Si todo lo que necesita son servicios centrales de Angular, es suficiente con cargarlos
ng
, así:Puede, por ejemplo, usar el
module.constant
mecanismo para hacer que los datos estén disponibles para su aplicación:Esto
myAppConfig
ahora se puede inyectar como cualquier otro servicio, y en particular está disponible durante la fase de configuración:o, para una aplicación más pequeña, puede inyectar la configuración global directamente en su servicio, a expensas de difundir el conocimiento sobre el formato de configuración en toda la aplicación.
Por supuesto, dado que las operaciones asincrónicas aquí bloquearán el arranque de la aplicación y, por lo tanto, bloquearán la compilación / vinculación de la plantilla, es aconsejable usar la
ng-cloak
directiva para evitar que la plantilla no analizada aparezca durante el trabajo. También podría proporcionar algún tipo de indicación de carga en el DOM, proporcionando algún HTML que se muestre solo hasta que AngularJS se inicialice:He creado un ejemplo completo, trabajando de este enfoque en Plunker, cargar la configuración de un archivo JSON estática como ejemplo.
fuente
Tuve el mismo problema: me encanta el
resolve
objeto, pero eso solo funciona para el contenido de ng-view. ¿Qué sucede si tiene controladores (para navegación de nivel superior, digamos) que existen fuera de ng-view y que necesitan inicializarse con datos antes de que el enrutamiento comience a suceder? ¿Cómo evitamos perder el tiempo en el lado del servidor solo para que eso funcione?Use bootstrap manual y una constante angular . Un ingenuo XHR le proporciona sus datos y arranca angularmente en su devolución de llamada, que se ocupa de sus problemas asincrónicos. En el ejemplo a continuación, ni siquiera necesita crear una variable global. Los datos devueltos existen solo en alcance angular como inyectables, y ni siquiera están presentes dentro de los controladores, servicios, etc., a menos que los inyecte. (Tanto como inyectaría la salida de su
resolve
objeto en el controlador para una vista enrutada). Si prefiere interactuar posteriormente con esos datos como un servicio, puede crear un servicio, inyectar los datos, y nadie será más sabio. .Ejemplo:
Ahora, tu
NavData
constante existe. Continúe e inyéctelo en un controlador o servicio:Por supuesto, el uso de un objeto XHR desnudo elimina algunas de las sutilezas que
$http
JQuery se encargaría de usted, pero este ejemplo funciona sin dependencias especiales, al menos para un simpleget
. Si desea un poco más de potencia para su solicitud, cargue una biblioteca externa para ayudarlo. Pero no creo que sea posible acceder a$http
herramientas angulares u otras en este contexto.(Publicación relacionada con SO )
fuente
Lo que puede hacer en su .config para la aplicación es crear el objeto de resolución para la ruta y en la función pasar $ q (objeto de promesa) y el nombre del servicio del que depende, y resolver la promesa en el función de devolución de llamada para $ http en el servicio de esta manera:
CONFIGURACIÓN DE RUTA
Angular no representará la plantilla ni hará que el controlador esté disponible hasta que se llame a defer.resolve (). Podemos hacer eso en nuestro servicio:
SERVICIO
Ahora que MyService tiene los datos asignados a su propiedad de datos, y la promesa en el objeto de resolución de ruta se ha resuelto, nuestro controlador para la ruta comienza a funcionar, y podemos asignar los datos del servicio a nuestro objeto de controlador.
CONTROLADOR
Ahora todos nuestros enlaces en el alcance del controlador podrán usar los datos que se originaron en MyService.
fuente
resolve
un objeto con una propiedad como función. así que terminó siendoresolve:{ dataFetch: function(){ // call function here } }
Entonces encontré una solución. Creé un servicio angularJS, lo llamaremos MyDataRepository y creé un módulo para él. Luego sirvo este archivo javascript desde mi controlador del lado del servidor:
HTML:
Lado del servidor:
Entonces puedo inyectar MyDataRepository donde lo necesite:
Esto funcionó muy bien para mí, pero estoy abierto a cualquier comentario si alguien tiene alguno. }
fuente
Además, puede usar las siguientes técnicas para aprovisionar su servicio a nivel mundial, antes de ejecutar los controladores reales: https://stackoverflow.com/a/27050497/1056679 . Simplemente resuelva sus datos globalmente y luego páselos a su servicio en
run
bloque, por ejemplo.fuente
Puedes usar
JSONP
para cargar asincrónicamente los datos del servicio. La solicitud JSONP se realizará durante la carga inicial de la página y los resultados estarán disponibles antes de que comience su aplicación. De esta manera, no tendrá que aumentar su enrutamiento con resoluciones redundantes.Tu HTML se vería así:
fuente
La forma más fácil de obtener cualquier inicialización utiliza el directorio ng-init.
Simplemente coloque el alcance div ng-init donde desee obtener datos de inicio
index.html
index.js
fuente