Se preguntó cuál es la mejor manera de detectar el final de la carga / arranque de la página, cuando todas las directivas terminan de compilar / vincular.
¿Algún evento ya está ahí? ¿Debo sobrecargar la función de arranque?
Se preguntó cuál es la mejor manera de detectar el final de la carga / arranque de la página, cuando todas las directivas terminan de compilar / vincular.
¿Algún evento ya está ahí? ¿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 después de que se hayan cargado las cosas. Apuesto a que mirar ngCloak conducirá a la respuesta exacta ...
EDITAR 1 hora después: Ok, bueno, miré ngCloak y es realmente corto. Lo que esto obviamente implica es que la función de compilación no se ejecutará hasta que se hayan evaluado las expresiones {{plantilla}} (es decir, la plantilla que cargó), por lo tanto, la buena funcionalidad de la directiva ngCloak.
Mi conjetura educada sería simplemente hacer una directiva con la misma simplicidad de ngCloak, luego, en su función de compilación, haga lo que quiera hacer. :) Coloque la directiva en el elemento raíz de su aplicación. Puede llamar a la directiva algo como myOnload y usarla como atributo my-onload. La función de compilación se ejecutará una vez que la plantilla haya sido compilada (expresiones evaluadas y sub-plantillas cargadas).
EDITAR, 23 horas después: Ok, 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, se explica a continuación) se ejecutará cuando su elemento esté listo / cargado. Basado en la descripción de Josh del orden en el que se ejecutan las funciones de compilación y enlace ,
si tiene este marcado:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Entonces AngularJS creará las directivas ejecutando funciones directivas en un orden determinado:
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 post-enlace, por lo que la función de enlace de la directiva externa1 no se ejecutará hasta que se haya ejecutado la función de enlace de la directiva interna2. Es por eso que decimos que solo es seguro realizar la manipulación DOM en el enlace posterior. Entonces, hacia la pregunta original, no debería haber ningún problema para acceder al html interno de la directiva secundaria desde la función de enlace de la directiva externa, aunque los contenidos insertados dinámicamente deben compilarse, como se dijo anteriormente.
De esto podemos concluir que simplemente podemos hacer una directiva para ejecutar nuestro código cuando todo esté listo / compilado / 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 se console.log
activará cuando se cargue:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
¡Es así de simple! Simplemente haga una directiva simple y úsela. ;)
Puede personalizarlo aún más para que pueda ejecutar una expresión (es decir, una función) agregándole $scope.$eval($attributes.ngElementReady);
:
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:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Solo asegúrese de tener sus funciones (por ejemplo, bodyIsReady y divIsReady) definidas en el alcance (en el controlador) bajo el cual vive su elemento.
Advertencias: dije que esto funcionaría para la mayoría de los casos. Tenga cuidado al usar ciertas directivas como ngRepeat y ngIf. Crean su propio alcance y es posible que su directiva no se active. 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 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, haciendo esto:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
en lugar de esto:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
La directiva ngElementReady se compilará en el último ejemplo, pero su función de enlace no se ejecutará. Nota: las directivas siempre se compilan, pero sus funciones de enlace no siempre se ejecutan dependiendo de ciertos escenarios como el anterior.
EDITAR, unos minutos después:
Ah, y para responder completamente a la pregunta, puede ahora $emit
o $broadcast
su evento desde la expresión o función que se ejecuta en el ng-element-ready
atributo. :) P.ej:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDITAR, incluso más minutos después:
La respuesta de @ satchmorun también funciona, pero solo para la carga inicial. Aquí hay una pregunta SO muy útil que describe el orden en que se ejecutan las cosas, incluidas las funciones de enlace app.run
, y otras. Entonces, 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, 17 de octubre a las 8:11 PST:
Esto no funciona con parciales que se cargan de forma asincrónica. Deberá agregar contabilidad a sus parciales (por ejemplo, una forma es hacer que cada parcial lleve un registro de cuándo se carga su contenido y luego emitir un evento para que el alcance principal pueda contar cuántos parciales se han cargado y finalmente hacer lo que necesita hacer después de cargar todos los parciales).
EDITAR, 23 de octubre a las 10:52 pm PST:
Hice una directiva simple para disparar 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 am PST:
Mejoré mi ngElementReady
directiva original y le cambié el nombre a whenReady
.
/*
* The whenReady directive allows you to execute the content of a when-ready
* attribute after the element is ready (i.e. 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 (i.e. {{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, utilícelo así para disparar someFunction
cuando un elemento está cargado y {{placeholders}}
aún no se ha reemplazado:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
se llamará antes de que item.property
se reemplacen todos los marcadores de posición.
Evalúe tantas expresiones como desee y haga que la última expresión true
a esperar {{placeholders}}
sea evaluada de esta manera:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
y anotherFunction
será despedido después de {{placeholders}}
haber sido reemplazado.
Esto solo funciona la primera vez que se carga un elemento, no en cambios futuros. Es posible que no funcione como se desea si $digest
sigue sucediendo después de que los marcadores de posición hayan sido reemplazados inicialmente (un $ digest puede ocurrir hasta 10 veces hasta que los datos dejen de cambiar). Será adecuado para la gran mayoría de casos de uso.
EDITAR, 31 de octubre a las 7:26 pm PST:
Muy 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 (i.e. when it's done loading all sub directives and DOM
* content). See: /programming/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 (i.e.
* {{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í
<div when-ready="isReady()" ready-check="checkIfReady()" wait-for-interpolation="true">
isReady will fire when this {{placeholder}} has been evaluated
and when checkIfReady finally returns true. checkIfReady might
contain code like `$('.some-element').length`.
</div>
Por supuesto, probablemente se pueda optimizar, pero lo dejaré así. requestAnimationFrame es agradable.
En los documentos de
angular.Module
, hay una entrada que describe larun
función:Entonces, si tiene algún módulo que sea su aplicación:
Puede ejecutar cosas después de que los módulos se hayan cargado con:
EDITAR: Inicialización manual al rescate.
Por lo tanto, se ha señalado que
run
no se llama cuando el DOM está listo y vinculado. Se llama cuando el$injector
módulo al que se hace referenciang-app
ha cargado todas sus dependencias, lo que es independiente del paso de compilación DOM.Eché otro vistazo a la inicialización manual , y parece que esto debería funcionar.
Hice un violín para ilustrar .
El HTML es simple:
Tenga en cuenta la falta de un
ng-app
. Y tengo una directiva que hará algo de manipulación DOM, para que podamos asegurarnos del orden y el tiempo de las cosas.Como es habitual, se crea un módulo:
Y aquí está la directiva:
Reemplazaremos la
test-directive
etiqueta con unadiv
de clasetest-directive
y envolveremos su contenido en unah1
.Agregué una función de compilación que devuelve funciones de enlace anteriores y posteriores para que podamos ver cuándo se ejecutan estas cosas.
Aquí está el resto del código:
Antes de que hayamos hecho algo, no debería haber elementos con una clase de
test-directive
en el DOM, y una vez que hayamos terminado, debería haber 1.Es bastante sencillo. Cuando el documento esté listo, llame
angular.bootstrap
con el elemento raíz de su aplicación y una matriz de nombres de módulo.De hecho, si adjunta una
run
función alapp
módulo , verá que se ejecuta antes de que se realice la compilación.Si ejecuta el violín y mira la consola, verá lo siguiente:
fuente
run
activa antes de la directiva y cuando se ejecuta, html no está todo allí$timeout( initMyPlugins,0)
Angular no ha proporcionado una forma de indicar cuando 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 a los demás. "Finalizar" significaría que se han cargado todos. Cualquier marco tendría dificultades para analizar su código y comprender que todo está hecho o que aún se espera. Para eso, tendría que proporcionar una lógica específica de la aplicación para verificarlo y determinarlo.
fuente
Se me ocurrió una solución que es relativamente precisa para evaluar cuándo se completa la inicialización angular.
La directiva es:
Eso puede agregarse como un atributo al
body
elemento y luego escucharse para usar$scope.$on('initialised', fn)
Funciona asumiendo que la aplicación se inicializa cuando no hay más ciclos de $ digest. $ watch se llama en cada ciclo de resumen y, por lo tanto, se inicia un temporizador (setTimeout no $ timeout, por lo que no se activa 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 precisa como la solución satchmoruns (ya que es posible que un ciclo de resumen demore más que el tiempo de espera), pero mi solución no necesita que realice un seguimiento de los módulos, lo que hace que sea mucho más fácil de administrar (especialmente para proyectos más grandes ). De todos modos, parece ser lo suficientemente preciso para mis requisitos. Espero eso ayude.
fuente
Si está utilizando Angular UI Router , puede escuchar el
$viewContentLoaded
evento."$ viewContentLoaded: se activa una vez que se carga la vista, después de que se procesa el DOM . El '$ scope' de la vista emite el evento". - Enlace
fuente
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 resumen de la aplicación) por ejemplo, espero que mi ng-repetidor produzca 7 resultados y ahí para mí establecerá una función de observación con la ayuda de setInterval para este propósito.
fuente
Si no usa el módulo ngRoute , es decir, no tiene el evento $ viewContentLoaded .
Puede utilizar otro método de directiva:
De acuerdo con la respuesta de trusktr, tiene la prioridad más baja. Además, $ timeout hará que Angular se ejecute a través de un ciclo de eventos completo antes de la ejecución de la devolución de llamada.
Se utilizó $ rootScope , porque permite colocar la directiva en cualquier ámbito de la aplicación y notificar solo a los oyentes necesarios.
fuente
Según el equipo de Angular y este problema de Github :
En base a esto, parece que actualmente no es posible hacerlo de una manera confiable, de lo contrario Angular habría proporcionado el evento listo para usar.
Arrancar la aplicación implica ejecutar el ciclo de resumen en el alcance raíz, y tampoco hay un evento de finalización del ciclo de resumen.
Según los documentos de diseño de Angular 2 :
De acuerdo con esto, el hecho de que esto no sea posible es una de las razones por las que se tomó la decisión de optar por una reescritura en Angular 2.
fuente
Tenía un fragmento que se estaba cargando después / por el parcial principal que ingresó a través del enrutamiento.
Necesitaba ejecutar una función después de que se cargó ese subparcial y no quería escribir una nueva directiva y descubrí que podría usar un descarado
ngIf
Controlador de padre parcial:
HTML de subparcial
fuente
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 reaccionar cuando todas las directivas hayan terminado de compilar / vincular, puede agregar las soluciones propuestas apropiadas arriba en la lógica de inicialización.
fuente
Todas estas son excelentes soluciones. Sin embargo, si actualmente está utilizando enrutamiento, encontré que esta solución es la más fácil y la menor cantidad de código necesaria. Usar la propiedad 'resolver' para esperar a que se complete una promesa antes de activar la ruta. p.ej
})
Haga clic aquí para ver los documentos completos - Crédito a K. Scott Allen
fuente
puede ser que pueda ayudarte con este ejemplo
En el fancybox personalizado muestro contenidos con valores interpolados.
en el servicio, en el método fancybox "abierto", lo hago
$ compile devuelve datos compilados. puedes comprobar los datos compilados
fuente