Evento de envío cuando angular.js terminó de cargarse

Me pregunté cuál es la mejor manera de detectar el final de la carga de páginas / bootstrapping, cuando todas las directivas terminaron de comstackr / enlazar.

¿Algún evento ya está allí? ¿Debo sobrecargar la función de arranque?

Solo una corazonada: ¿por qué no mirar cómo lo hace la directiva ngCloak? Claramente, la directiva ngCloak logra mostrar contenido una vez que las cosas se han cargado. Apuesto a que mirar a ngCloak nos llevará a la respuesta exacta …

EDITAR 1 hora más tarde: Ok, bueno, miré ngCloak y es realmente corto. Lo que esto obviamente implica es que la función de comstackción no se ejecutará hasta que se hayan evaluado las expresiones {{template}} (es decir, la plantilla que cargó), por lo tanto, la buena funcionalidad de la directiva ngCloak.

Mi conjetura sería simplemente hacer una directiva con la misma simplicidad de ngCloak, luego en su función de comstackción haga lo que quiera hacer. 🙂 Coloque la directiva en el elemento raíz de su aplicación. Puede llamar a la directiva algo así como myOnload y usarlo como un atributo my-onload. La función de comstackción se ejecutará una vez que la plantilla haya sido comstackda (expresiones evaluadas y sub-plantillas cargadas).

EDITAR, 23 horas después: Ok, entonces investigué un poco, y también hice mi propia pregunta . La pregunta que hice estaba indirectamente relacionada con esta pregunta, pero casualmente me llevó a la respuesta que resuelve esta pregunta.

La respuesta es que puede crear una directiva simple y poner su código en la función de enlace de la directiva, que (para la mayoría de los casos de uso, explicados a continuación) se ejecutará cuando su elemento esté listo / cargado. Según la descripción de Josh del orden en que se ejecutan las funciones de comstackción y enlace ,

si tienes este marcado:

Entonces AngularJS creará las directivas ejecutando funciones directivas en un cierto orden:

 directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link 

Por defecto, una función de “enlace” directo es un enlace posterior, por lo que la función de enlace de su directiva externa1 no se ejecutará hasta después de que se haya ejecutado la función de enlace de la directiva interna2. Es por eso que decimos que solo es seguro hacer la manipulación de DOM en el enlace posterior. Por lo tanto, hacia la pregunta original, no debería haber ningún problema para acceder al html interno de la directiva hija desde la función de enlace de la directiva externa, aunque se deben comstackr los contenidos insertados dinámicamente, como se dijo anteriormente.

De esto podemos concluir que simplemente podemos hacer una directiva para ejecutar nuestro código cuando todo está listo / comstackdo / vinculado / cargado:

  app.directive('ngElementReady', [function() { return { priority: -1000, // a low number so this directive loads after all other directives have loaded. restrict: "A", // attribute only link: function($scope, $element, $attributes) { console.log(" -- Element ready!"); // do what you want here. } }; }]); 

Ahora lo que puede hacer es colocar la directiva ngElementReady en el elemento raíz de la aplicación, y console.log activará cuando se cargue:

  ... ...  

¡Es así de simple! Simplemente haga una directiva simple y úselo. 😉

Puede personalizarlo aún más para que pueda ejecutar una expresión (es decir, una función) agregando $scope.$eval($attributes.ngElementReady); lo:

  app.directive('ngElementReady', [function() { return { priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any. restrict: "A", link: function($scope, $element, $attributes) { $scope.$eval($attributes.ngElementReady); // execute the expression in the attribute. } }; }]); 

Entonces puedes usarlo en cualquier elemento:

  ... 
...

Solo asegúrate de que tienes tus funciones (por ejemplo, bodyIsReady y divIsReady) definidas en el scope (en el controlador) en el que vive tu elemento.

Advertencias: Dije que esto funcionará para la mayoría de los casos. Tenga cuidado al usar ciertas directivas como ngRepeat y ngIf. Crean su propio scope, y su directiva no puede disparar. Por ejemplo, si coloca nuestra nueva directiva ngElementReady en un elemento que también tiene ngIf, y la condición de ngIf se evalúa como falsa, entonces nuestra directiva ngElementReady no se cargará. O, por ejemplo, si coloca nuestra nueva directiva ngElementReady en un elemento que también tiene una directiva ngInclude, nuestra directiva no se cargará si la plantilla para el ngInclude no existe. Puede solucionar algunos de estos problemas asegurándose de anidar las directivas en lugar de ponerlas todas en el mismo elemento. Por ejemplo, al hacer esto:

 

en lugar de esto:

 

La directiva ngElementReady se comstackrá en el último ejemplo, pero su función de enlace no se ejecutará. Nota: las directivas siempre se comstackn, pero sus funciones de enlace no siempre se ejecutan en función de ciertos escenarios como el anterior.

EDITAR, unos minutos más tarde:

Ah, y para responder completamente a la pregunta, ahora puede $emit o $broadcast su evento desde la expresión o función que se ejecuta en el atributo ng-element-ready . 🙂 P.ej:

 
...

EDITAR, incluso más minutos después:

La respuesta de @schchmorun también funciona, pero solo para la carga inicial. Aquí hay una pregunta de SO muy útil que describe el orden en que se ejecutan las cosas, incluidas las funciones de enlace, app.run y otras. Por lo tanto, dependiendo de su caso de uso, app.run podría ser bueno, pero no para elementos específicos, en cuyo caso las funciones de enlace son mejores.

EDITAR, cinco meses después, el 17 de octubre a las 8:11 PST:

Esto no funciona con parciales que se cargan de forma asíncrona. Necesitará agregar la contabilidad en sus parciales (por ejemplo, una forma es hacer que cada parcial realice un seguimiento de cuándo termina su carga y luego emita un evento para que el scope principal pueda contar cuántos parciales se han cargado y finalmente hacer lo que necesita para hacer después de que todos los parciales estén cargados).

EDITAR, 23 de octubre a las 10:52 p.m. PST:

Hice una directiva simple para activar un código cuando se carga una imagen:

 /* * This img directive makes it so that if you put a loaded="" attribute on any * img element in your app, the expression of that attribute will be evaluated * after the images has finished loading. Use this to, for example, remove * loading animations after images have finished loading. */ app.directive('img', function() { return { restrict: 'E', link: function($scope, $element, $attributes) { $element.bind('load', function() { if ($attributes.loaded) { $scope.$eval($attributes.loaded); } }); } }; }); 

EDITAR, 24 de octubre a las 12:48 a.m. PST:

ngElementReady mi directiva original ngElementReady y la whenReady .

 /* * The whenReady directive allows you to execute the content of a when-ready * attribute after the element is ready (ie done loading all sub directives and DOM * content except for things that load asynchronously like partials and images). * * Execute multiple expressions by delimiting them with a semi-colon. If there * is more than one expression, and the last expression evaluates to true, then * all expressions prior will be evaluated after all text nodes in the element * have been interpolated (ie {{placeholders}} replaced with actual values). * * Caveats: if other directives exists on the same element as this directive * and destroy the element thus preventing other directives from loading, using * this directive won't work. The optimal way to use this is to put this * directive on an outer element. */ app.directive('whenReady', ['$interpolate', function($interpolate) { return { restrict: 'A', priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any. link: function($scope, $element, $attributes) { var expressions = $attributes.whenReady.split(';'); var waitForInterpolation = false; function evalExpressions(expressions) { expressions.forEach(function(expression) { $scope.$eval(expression); }); } if ($attributes.whenReady.trim().length == 0) { return; } if (expressions.length > 1) { if ($scope.$eval(expressions.pop())) { waitForInterpolation = true; } } if (waitForInterpolation) { requestAnimationFrame(function checkIfInterpolated() { if ($element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}} requestAnimationFrame(checkIfInterpolated); } else { evalExpressions(expressions); } }); } else { evalExpressions(expressions); } } } }]); 

Por ejemplo, someFunction así para disparar someFunction cuando se carga un elemento y {{placeholders}} aún no reemplazado:

 
{{item.property}}

someFunction alguna función antes de que se item.property todos los marcadores de posición item.property .

Evalúe tantas expresiones como desee y haga que la última expresión true para esperar que {{placeholders}} se evalúe de esta manera:

 
{{item.property}}

someFunction y otra anotherFunction se dispararán después de que se hayan reemplazado {{placeholders}} .

Esto solo funciona la primera vez que se carga un elemento, no en futuros cambios. Puede no funcionar como se desea si un $digest sigue sucediendo después de que los marcadores de posición hayan sido reemplazados inicialmente (un resumen de $ puede ocurrir hasta 10 veces hasta que los datos dejen de cambiar). Será adecuado para la gran mayoría de los casos de uso.

EDITAR, 31 de octubre a las 7:26 p.m. PST:

Bien, esta es probablemente mi última y última actualización. Esto probablemente funcionará para 99.999 de los casos de uso que existen:

 /* * The whenReady directive allows you to execute the content of a when-ready * attribute after the element is ready (ie when it's done loading all sub directives and DOM * content). See: https://stackoverflow.com/questions/14968690/sending-event-when-angular-js-finished-loading * * Execute multiple expressions in the when-ready attribute by delimiting them * with a semi-colon. when-ready="doThis(); doThat()" * * Optional: If the value of a wait-for-interpolation attribute on the * element evaluates to true, then the expressions in when-ready will be * evaluated after all text nodes in the element have been interpolated (ie * {{placeholders}} have been replaced with actual values). * * Optional: Use a ready-check attribute to write an expression that * specifies what condition is true at any given moment in time when the * element is ready. The expression will be evaluated repeatedly until the * condition is finally true. The expression is executed with * requestAnimationFrame so that it fires at a moment when it is least likely * to block rendering of the page. * * If wait-for-interpolation and ready-check are both supplied, then the * when-ready expressions will fire after interpolation is done *and* after * the ready-check condition evaluates to true. * * Caveats: if other directives exists on the same element as this directive * and destroy the element thus preventing other directives from loading, using * this directive won't work. The optimal way to use this is to put this * directive on an outer element. */ app.directive('whenReady', ['$interpolate', function($interpolate) { return { restrict: 'A', priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any. link: function($scope, $element, $attributes) { var expressions = $attributes.whenReady.split(';'); var waitForInterpolation = false; var hasReadyCheckExpression = false; function evalExpressions(expressions) { expressions.forEach(function(expression) { $scope.$eval(expression); }); } if ($attributes.whenReady.trim().length === 0) { return; } if ($attributes.waitForInterpolation && $scope.$eval($attributes.waitForInterpolation)) { waitForInterpolation = true; } if ($attributes.readyCheck) { hasReadyCheckExpression = true; } if (waitForInterpolation || hasReadyCheckExpression) { requestAnimationFrame(function checkIfReady() { var isInterpolated = false; var isReadyCheckTrue = false; if (waitForInterpolation && $element.text().indexOf($interpolate.startSymbol()) >= 0) { // if the text still has {{placeholders}} isInterpolated = false; } else { isInterpolated = true; } if (hasReadyCheckExpression && !$scope.$eval($attributes.readyCheck)) { // if the ready check expression returns false isReadyCheckTrue = false; } else { isReadyCheckTrue = true; } if (isInterpolated && isReadyCheckTrue) { evalExpressions(expressions); } else { requestAnimationFrame(checkIfReady); } }); } else { evalExpressions(expressions); } } }; }]); 

Úselo así

 
isReady will fire when this {{placeholder}} has been evaluated and when checkIfReady finally returns true. checkIfReady might contain code like `$('.some-element').length`.

Por supuesto, probablemente se pueda optimizar, pero lo dejaré así. requestAnimationFrame es agradable.

En los documentos para angular.Module , hay una entrada que describe la función de run :

Utilice este método para registrar el trabajo que debe realizarse cuando el inyector termina de cargar todos los módulos.

Entonces, si tienes algún módulo que sea tu aplicación:

 var app = angular.module('app', [/* module dependencies */]); 

Puede ejecutar cosas una vez que los módulos se hayan cargado con:

 app.run(function() { // Do post-load initialization stuff here }); 

EDITAR: Inicialización manual al rescate

Por lo tanto, se ha señalado que la run no se llama cuando el DOM está listo y vinculado. Se llama cuando $injector para el módulo referenciado por ng-app ha cargado todas sus dependencias, que es independiente del paso de comstackción DOM.

Eché otro vistazo a la inicialización manual , y parece que esto debería ser el truco.

He hecho un violín para ilustrar .

El HTML es simple:

   This is a test   

Tenga en cuenta la falta de una ng-app . Y tengo una directiva que hará algo de manipulación DOM, para que podamos asegurar el orden y el tiempo de las cosas.

Como de costumbre, se crea un módulo:

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

Y esta es la directiva:

 app.directive('testDirective', function() { return { restrict: 'E', template: '

', replace: true, transclude: true, compile: function() { console.log("Compiling test-directive"); return { pre: function() { console.log("Prelink"); }, post: function() { console.log("Postlink"); } }; } }; });

Reemplazaremos la etiqueta test-directive por una test-directive de test-directive div of class, y envolveremos su contenido en h1 .

Agregué una función de comstackción que devuelve las funciones de enlace previo y posterior para que podamos ver cuándo se ejecutan estas cosas.

Aquí está el rest del código:

 // The bootstrapping process var body = document.getElementsByTagName('body')[0]; // Check that our directive hasn't been compiled function howmany(classname) { return document.getElementsByClassName(classname).length; } 

Antes de que hayamos hecho algo, no debería haber elementos con una clase de test-directive de test-directive en el DOM, y una vez que hayamos terminado, debería haber 1.

 console.log('before (should be 0):', howmany('test-directive')); angular.element(document).ready(function() { // Bootstrap the body, which loades the specified modules // and compiled the DOM. angular.bootstrap(body, ['app']); // Our app is loaded and the DOM is compiled console.log('after (should be 1):', howmany('test-directive')); }); 

Es bastante sencillo. Cuando el documento esté listo, llame a angular.bootstrap con el elemento raíz de su aplicación y una matriz de nombres de módulos.

De hecho, si adjuntas una función de run al módulo de la app , verás que se ejecuta antes de que se realice la comstackción.

Si ejecuta el violín y mira la consola, verá lo siguiente:

 before (should be 0): 0 Compiling test-directive Prelink Postlink after (should be 1): 1 < --- success! 

Quería agregar una realización más que tenía sobre mi propia pregunta: Angular no ha proporcionado una forma de señalar cuándo una página terminó de cargarse, tal vez porque ‘terminado’ depende de su aplicación . por ejemplo, si tiene un árbol jerárquico de parciales, uno carga los otros. ‘finalizar’ significaría que todos ellos han sido cargados. Cualquier marco tendría dificultades para analizar su código y entender que todo está hecho, o aún esperado. Para eso, tendría que proporcionar la lógica específica de la aplicación para verificar y determinar eso.

Gracias

Lior

He encontrado una solución que es relativamente precisa para evaluar cuándo se completa la inicialización angular.

La directiva es:

 .directive('initialisation',['$rootScope',function($rootScope) { return { restrict: 'A', link: function($scope) { var to; var listener = $scope.$watch(function() { clearTimeout(to); to = setTimeout(function () { console.log('initialised'); listener(); $rootScope.$broadcast('initialised'); }, 50); }); } }; }]); 

Eso puede simplemente agregarse como un atributo al elemento del body y luego escuchar usando $scope.$on('initialised', fn)

Funciona suponiendo que la aplicación se inicializa cuando no hay más ciclos de $ digest. $ watch se llama cada ciclo de resumen y se inicia un temporizador (setTimeout not $ timeout para que no se desencadene un nuevo ciclo de resumen). Si no se produce un ciclo de resumen dentro del tiempo de espera, se supone que la aplicación se ha inicializado.

Obviamente no es tan preciso como la solución de satchmoruns (ya que es posible que un ciclo de resumen tarde más tiempo que el tiempo de espera) pero mi solución no necesita que haga un seguimiento de los módulos, lo que hace que sea mucho más fácil de administrar (especialmente para proyectos grandes ) De todos modos, parece ser lo suficientemente preciso para mis requerimientos. Espero eso ayude.

Si está utilizando el enrutador de UI angular , puede escuchar el evento $viewContentLoaded .

“$ viewContentLoaded: se activa una vez que se carga la vista, después de que se muestra el DOM . El ‘$ scope’ de la vista emite el evento.” – Enlace

 $scope.$on('$viewContentLoaded', function(event){ ... }); 

observo la manipulación DOM de angular con JQuery y establecí un final para mi aplicación (algún tipo de situación predefinida y satisfactoria que necesito para mi aplicación-resumen) por ejemplo, espero que mi ng-repetidor produzca 7 resultados y allí para mí establecerá una función de observación con la ayuda de setInterval para este propósito.

 $(document).ready(function(){ var interval = setInterval(function(){ if($("article").size() == 7){ myFunction(); clearInterval(interval); } },50); }); 

Si no usa el módulo ngRoute , es decir, no tiene el evento $ viewContentLoaded .

Puedes usar otro método directivo:

  angular.module('someModule') .directive('someDirective', someDirective); someDirective.$inject = ['$rootScope', '$timeout']; //Inject services function someDirective($rootScope, $timeout){ return { restrict: "A", priority: Number.MIN_SAFE_INTEGER, //Lowest priority link : function(scope, element, attr){ $timeout( function(){ $rootScope.$emit("Some:event"); } ); } }; } 

De acuerdo con la respuesta de trusktr tiene la prioridad más baja. Además, $ timeout hará que Angular ejecute un bucle de evento completo antes de la ejecución de la callback.

$ rootScope utilizado, porque permite ubicar directivas en cualquier ámbito de la aplicación y notificar únicamente a los oyentes necesarios.

$ rootScope. $ emitirá un evento para todos los $ rootScope. $ en los oyentes solamente. La parte interesante es que $ rootScope. $ Broadcast notificará todos los $ rootScope. $ On así como $ scope. $ En los oyentes Fuente

De acuerdo con el equipo Angular y este problema de Github :

ahora tenemos $ viewContentLoaded y $ includeContentLoaded eventos que se emiten en ng-view y ng-include respectivamente. Creo que esto es lo más cercano que se puede llegar a saber cuando hayamos terminado con la comstackción.

En base a esto, parece que actualmente no es posible hacerlo de manera confiable, de lo contrario, Angular habría proporcionado el evento de la caja.

Arrastrar la aplicación implica ejecutar el ciclo de resumen en el ámbito de la raíz, y tampoco hay un evento terminado ciclo de resumen.

De acuerdo con los documentos de diseño Angular 2:

Debido a múltiples resúmenes, es imposible determinar y notificar al componente que el modelo es estable. Esto se debe a que la notificación puede cambiar aún más los datos, lo que puede reiniciar el proceso de enlace.

De acuerdo con esto, el hecho de que esto no sea posible es una de las razones por las cuales se tomó la decisión de buscar una reescritura en Angular 2.

Tenía un fragmento que se cargaba después / por el parcial principal que venía a través del enrutamiento.

Necesitaba ejecutar una función después de que ese subparto se cargara y no quería escribir una nueva directiva y me di cuenta de que podía usar un ngIf descarado ngIf

Controlador del pariente primario:

 $scope.subIsLoaded = function() { /*do stuff*/; return true; }; 

HTML de subpartial

  

Si desea generar JS con datos del lado del servidor (JSP, PHP) puede agregar su lógica a un servicio, que se cargará automáticamente cuando se cargue su controlador.

Además, si desea reactjsr cuando todas las directivas se completan comstackndo / vinculando, puede agregar las soluciones propuestas apropiadas arriba en la lógica de inicialización.

 module.factory('YourControllerInitService', function() { // add your initialization logic here // return empty service, because it will not be used return {}; }); module.controller('YourController', function (YourControllerInitService) { }); 

Todas estas son excelentes soluciones. Sin embargo, si actualmente está utilizando el enrutamiento, entonces encontré que esta solución es la más fácil y la menos necesaria. Usar la propiedad ‘resolver’ para esperar a que se complete una promesa antes de activar la ruta. p.ej

 $routeProvider .when("/news", { templateUrl: "newsView.html", controller: "newsController", resolve: { message: function(messageService){ return messageService.getMessage(); } } 

})

Haga clic aquí para ver los documentos completos – Crédito a K. Scott Allen

puede ser que puedo ayudarte con este ejemplo

En la fancybox personalizada, muestro contenido con valores interpolados.

en el servicio, en el método “abierto” de fancybox, lo hago

 open: function(html, $compile) { var el = angular.element(html); var compiledEl = $compile(el); $.fancybox.open(el); } 

la comstackción $ devuelve datos comstackdos. puedes verificar los datos comstackdos