Je me suis demandé quel était le meilleur moyen de détecter la fin du chargement / démarrage de la page, lorsque toutes les directives avaient terminé la compilation / la liaison.
Un événement déjà là? Dois-je surcharger la fonction bootstrap?
Je me suis demandé quel était le meilleur moyen de détecter la fin du chargement / démarrage de la page, lorsque toutes les directives avaient terminé la compilation / la liaison.
Un événement déjà là? Dois-je surcharger la fonction bootstrap?
Juste une intuition: pourquoi ne pas regarder comment la directive ngCloak le fait? Il est clair que la directive ngCloak parvient à afficher le contenu après le chargement des éléments. Je parie que regarder ngCloak mènera à la réponse exacte ...
EDIT 1 heure plus tard: Ok, eh bien, j'ai regardé ngCloak et c'est vraiment court. Ce que cela implique évidemment, c'est que la fonction de compilation ne sera pas exécutée tant que les expressions {{template}} n'auront pas été évaluées (c'est-à-dire le modèle qu'elle a chargé), d'où la fonctionnalité intéressante de la directive ngCloak.
Ma supposition éclairée serait simplement de créer une directive avec la même simplicité que ngCloak, puis dans votre fonction de compilation, faites ce que vous voulez faire. :) Placez la directive sur l'élément racine de votre application. Vous pouvez appeler la directive quelque chose comme myOnload et l'utiliser comme attribut my-onload. La fonction de compilation s'exécutera une fois le modèle compilé (expressions évaluées et sous-modèles chargés).
EDIT, 23 heures plus tard: Ok, donc j'ai fait quelques recherches, et j'ai aussi posé ma propre question . La question que j'ai posée était indirectement liée à cette question, mais elle m'a conduit par hasard à la réponse qui résout cette question.
La réponse est que vous pouvez créer une directive simple et placer votre code dans la fonction de lien de la directive, qui (pour la plupart des cas d'utilisation, expliqués ci-dessous) s'exécutera lorsque votre élément sera prêt / chargé. Sur la base de la description de Josh de l'ordre dans lequel les fonctions de compilation et de liaison sont exécutées ,
si vous avez ce balisage:
<div directive1> <div directive2> <!-- ... --> </div> </div>
Ensuite, AngularJS créera les directives en exécutant les fonctions de directive dans un certain ordre:
directive1: compile directive2: compile directive1: controller directive1: pre-link directive2: controller directive2: pre-link directive2: post-link directive1: post-link
Par défaut, une fonction de «lien» directe est un post-lien, donc la fonction de lien de votre directive externe1 ne fonctionnera qu'après l'exécution de la fonction de lien de la directive interne2. C'est pourquoi nous disons qu'il n'est sûr de faire de la manipulation DOM que dans le post-lien. Donc, pour la question initiale, il ne devrait y avoir aucun problème pour accéder au code HTML interne de la directive enfant à partir de la fonction de lien de la directive externe, bien que le contenu inséré dynamiquement doit être compilé, comme indiqué ci-dessus.
De cela, nous pouvons conclure que nous pouvons simplement créer une directive pour exécuter notre code lorsque tout est prêt / compilé / lié / chargé:
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.
}
};
}]);
Maintenant, ce que vous pouvez faire est de mettre la directive ngElementReady sur l'élément racine de l'application, et le console.log
déclenchera lorsqu'il sera chargé:
<body data-ng-app="MyApp" data-ng-element-ready="">
...
...
</body>
C'est si simple! Créez simplement une directive simple et utilisez-la. ;)
Vous pouvez le personnaliser davantage afin qu'il puisse exécuter une expression (c'est-à-dire une fonction) en y ajoutant $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.
}
};
}]);
Ensuite, vous pouvez l'utiliser sur n'importe quel élément:
<body data-ng-app="MyApp" data-ng-controller="BodyCtrl" data-ng-element-ready="bodyIsReady()">
...
<div data-ng-element-ready="divIsReady()">...<div>
</body>
Assurez-vous simplement que vos fonctions (par exemple bodyIsReady et divIsReady) sont définies dans la portée (dans le contrôleur) sous laquelle vit votre élément.
Avertissements: j'ai dit que cela fonctionnera dans la plupart des cas. Soyez prudent lorsque vous utilisez certaines directives comme ngRepeat et ngIf. Ils créent leur propre champ d'application et votre directive ne peut pas être déclenchée. Par exemple, si vous mettez notre nouvelle directive ngElementReady sur un élément qui a également ngIf et que la condition de ngIf est évaluée à false, alors notre directive ngElementReady ne sera pas chargée. Ou, par exemple, si vous placez notre nouvelle directive ngElementReady sur un élément qui a également une directive ngInclude, notre directive ne sera pas chargée si le modèle pour le ngInclude n'existe pas. Vous pouvez contourner certains de ces problèmes en vous assurant d'imbriquer les directives au lieu de les placer toutes sur le même élément. Par exemple, en faisant ceci:
<div data-ng-element-ready="divIsReady()">
<div data-ng-include="non-existent-template.html"></div>
<div>
au lieu de cela:
<div data-ng-element-ready="divIsReady()" data-ng-include="non-existent-template.html"></div>
La directive ngElementReady sera compilée dans le dernier exemple, mais sa fonction de liaison ne sera pas exécutée. Remarque: les directives sont toujours compilées, mais leurs fonctions de lien ne sont pas toujours exécutées selon certains scénarios comme ci-dessus.
EDIT, quelques minutes plus tard:
Oh, et pour répondre pleinement à la question, vous pouvez maintenant $emit
ou $broadcast
votre événement à partir de l'expression ou de la fonction qui est exécutée dans l' ng-element-ready
attribut. :) Par exemple:
<div data-ng-element-ready="$emit('someEvent')">
...
<div>
EDIT, encore plus quelques minutes plus tard:
La réponse de @ satchmorun fonctionne aussi, mais uniquement pour le chargement initial. Voici une question SO très utile qui décrit l'ordre dans lequel les choses sont exécutées, y compris les fonctions de lien app.run
, etc. Donc, en fonction de votre cas d'utilisation, cela app.run
peut être bon, mais pas pour des éléments spécifiques, auquel cas les fonctions de lien sont meilleures.
EDIT, cinq mois plus tard, le 17 octobre à 8h11 PST:
Cela ne fonctionne pas avec les partiels chargés de manière asynchrone. Vous devrez ajouter la comptabilité dans vos partiels (par exemple, une façon est de faire en sorte que chaque partiel garde une trace de la fin du chargement de son contenu, puis émet un événement afin que la portée parent puisse compter le nombre de partiels chargés et enfin faire ce dont il a besoin pour faire après que tous les partiels sont chargés).
EDIT, 23 octobre à 22h52 PST:
J'ai fait une directive simple pour déclencher du code lorsqu'une image est chargée:
/*
* 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);
}
});
}
};
});
EDIT, 24 octobre à 12h48 PST:
J'ai amélioré ma ngElementReady
directive d' origine et l' ai renommée 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);
}
}
}
}]);
Par exemple, utilisez-le comme ceci pour se déclencher someFunction
lorsqu'un élément est chargé et {{placeholders}}
pas encore remplacé:
<div when-ready="someFunction()">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
sera appelé avant que tous les item.property
espaces réservés soient remplacés.
Évaluez autant d'expressions que vous le souhaitez et créez la dernière expression true
à attendre pour {{placeholders}}
être évaluée comme ceci:
<div when-ready="someFunction(); anotherFunction(); true">
<span ng-repeat="item in items">{{item.property}}</span>
</div>
someFunction
et anotherFunction
sera tiré après {{placeholders}}
avoir été remplacé.
Cela ne fonctionne que la première fois qu'un élément est chargé, pas sur les modifications futures. Cela peut ne pas fonctionner comme vous le souhaitez si un $digest
continue à se produire après le remplacement initial des espaces réservés (un $ digest peut se produire jusqu'à 10 fois jusqu'à ce que les données cessent de changer). Il conviendra à une grande majorité de cas d'utilisation.
EDIT, le 31 octobre à 19h26 PST:
Très bien, c'est probablement ma dernière et dernière mise à jour. Cela fonctionnera probablement pour 99,999 des cas d'utilisation:
/*
* 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);
}
}
};
}]);
Utilisez-le comme ça
<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>
Bien sûr, il peut probablement être optimisé, mais je vais simplement en rester là. requestAnimationFrame est sympa.
Dans la documentation pour
angular.Module
, il y a une entrée décrivant larun
fonction:Donc, si vous avez un module qui est votre application:
Vous pouvez exécuter des choses une fois que les modules ont été chargés avec:
EDIT: Initialisation manuelle à la rescousse
Il a donc été souligné que le
run
n'est pas appelé lorsque le DOM est prêt et lié. Il est appelé lorsque le$injector
pour le module référencé parng-app
a chargé toutes ses dépendances, ce qui est distinct de l'étape de compilation DOM.J'ai jeté un autre regard sur l' initialisation manuelle , et il semble que cela devrait faire l'affaire.
J'ai fait un violon pour illustrer .
Le HTML est simple:
Notez l'absence d'un fichier
ng-app
. Et j'ai une directive qui fera quelques manipulations DOM, afin que nous puissions nous assurer de l'ordre et du timing des choses.Comme d'habitude, un module est créé:
Et voici la directive:
Nous allons remplacer la
test-directive
balise par undiv
of classtest-directive
et envelopper son contenu dans un fichierh1
.J'ai ajouté une fonction de compilation qui renvoie à la fois les fonctions de lien avant et après afin que nous puissions voir quand ces choses s'exécutent.
Voici le reste du code:
Avant de faire quoi que ce soit, il ne devrait y avoir aucun élément avec une classe de
test-directive
dans le DOM, et une fois que nous avons terminé, il devrait y avoir 1.C'est assez simple. Lorsque le document est prêt, appelez
angular.bootstrap
avec l'élément racine de votre application et un tableau de noms de modules.En fait, si vous attachez une
run
fonction auapp
module , vous verrez qu'elle est exécutée avant que la compilation n'ait lieu.Si vous exécutez le violon et regardez la console, vous verrez ce qui suit:
la source
run
déclenche avant la directive et lorsque l'exécution se déclenche, html n'est pas tout là$timeout( initMyPlugins,0)
travaux dans ma directive, tout le html dont j'ai besoin est làAngular n'a pas fourni de moyen de signaler la fin du chargement d'une page, peut-être parce que «terminé» dépend de votre application . Par exemple, si vous avez une arborescence hiérarchique de partiels, l'un chargeant les autres. "Terminer" signifierait que tous ont été chargés. Tout framework aurait du mal à analyser votre code et à comprendre que tout est fait, ou encore attendu. Pour cela, vous devrez fournir une logique spécifique à l'application pour vérifier et déterminer cela.
la source
J'ai trouvé une solution relativement précise pour évaluer le moment où l'initialisation angulaire est terminée.
La directive est:
Cela peut ensuite être simplement ajouté en tant qu'attribut à l'
body
élément, puis écouté pour l'utilisation$scope.$on('initialised', fn)
Cela fonctionne en supposant que l'application est initialisée lorsqu'il n'y a plus de cycles $ digest. $ watch est appelé à chaque cycle de résumé et donc un minuteur est démarré (setTimeout et non $ timeout donc un nouveau cycle de résumé n'est pas déclenché). Si un cycle de résumé ne se produit pas dans le délai imparti, l'application est supposée avoir été initialisée.
Ce n'est évidemment pas aussi précis que la solution satchmoruns (car il est possible qu'un cycle de synthèse prenne plus de temps que le délai d'expiration) mais ma solution n'a pas besoin que vous gardiez une trace des modules, ce qui le rend d'autant plus facile à gérer (en particulier pour les projets plus importants ). Quoi qu'il en soit, semble être suffisamment précis pour mes besoins. J'espère que ça aide.
la source
Si vous utilisez Angular UI Router , vous pouvez écouter l'
$viewContentLoaded
événement."$ viewContentLoaded - déclenché une fois la vue chargée, après le rendu du DOM . Le '$ scope' de la vue émet l'événement." - Lien
la source
J'observe la manipulation DOM de angular avec JQuery et j'ai défini une finition pour mon application (une sorte de situation prédéfinie et satisfaisante dont j'ai besoin pour mon résumé d'application) par exemple, je m'attends à ce que mon ng-répéteur produise 7 résultats et là pour i va définir une fonction d'observation à l'aide de setInterval à cet effet.
la source
Si vous n'utilisez pas le module ngRoute , c'est-à-dire que vous n'avez pas d' événement $ viewContentLoaded .
Vous pouvez utiliser une autre méthode de directive:
En conséquence, à la réponse de trusktr, il a la priorité la plus basse. De plus, $ timeout fera exécuter Angular à travers une boucle d'événements entière avant l'exécution du rappel.
$ rootScope utilisé, car il permet de placer une directive dans n'importe quelle portée de l'application et de ne notifier que les écouteurs nécessaires.
la source
Selon l'équipe Angular et ce problème Github :
Sur cette base, il semble que cela ne soit actuellement pas possible de le faire de manière fiable, sinon Angular aurait fourni l'événement prêt à l'emploi.
L'amorçage de l'application implique l'exécution du cycle de résumé sur l'étendue racine, et il n'y a pas non plus d'événement de fin de cycle de résumé.
Selon les documents de conception Angular 2 :
Selon cela, le fait que cela ne soit pas possible est l'une des raisons pour lesquelles la décision a été prise de procéder à une réécriture dans Angular 2.
la source
J'avais un fragment qui était chargé après / par le partiel principal qui arrivait via le routage.
J'avais besoin d'exécuter une fonction après le chargement de cette sous-partie et je ne voulais pas écrire une nouvelle directive et j'ai compris que vous pouviez utiliser un effronté
ngIf
Contrôleur du parent partiel:
HTML du sous-partiel
la source
Si vous souhaitez générer JS avec des données côté serveur (JSP, PHP), vous pouvez ajouter votre logique à un service, qui sera chargé automatiquement lorsque votre contrôleur est chargé.
De plus, si vous souhaitez réagir lorsque toutes les directives sont compilées / liées, vous pouvez ajouter les solutions proposées ci-dessus dans la logique d'initialisation.
la source
Ce sont toutes d'excellentes solutions.Cependant, si vous utilisez actuellement le routage, j'ai trouvé que cette solution était la quantité de code la plus simple et la moins nécessaire. Utilisation de la propriété 'resolution' pour attendre qu'une promesse se termine avant de déclencher la route. par exemple
})
Cliquez ici pour consulter la documentation complète - Crédit à K. Scott Allen
la source
peut-être que je peux vous aider par cet exemple
Dans la fancybox personnalisée, j'affiche le contenu avec des valeurs interpolées.
dans le service, dans la méthode "open" fancybox, je fais
le $ compile renvoie des données compilées. vous pouvez vérifier les données compilées
la source