Envoi d'un événement lorsque AngularJS a terminé le chargement

114

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?

Lior
la source

Réponses:

204

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.logdé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 $emitou $broadcastvotre événement à partir de l'expression ou de la fonction qui est exécutée dans l' ng-element-readyattribut. :) 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.runpeut ê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 ngElementReadydirective 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 someFunctionlorsqu'un élément est chargé et {{placeholders}}pas encore remplacé:

<div when-ready="someFunction()">
  <span ng-repeat="item in items">{{item.property}}</span>
</div>

someFunctionsera appelé avant que tous les item.propertyespaces 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>

someFunctionet anotherFunctionsera 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 $digestcontinue à 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.

trusktr
la source
3
Vraiment ennuyeux avec tous ces préfixes "data-". Je suis content de ne pas les utiliser moi-même.
stolsvik
1
@stolsvik heh, oui, dans les navigateurs les plus modernes, ils ne sont pas nécessaires.
trusktr
49
Mérite un vote pour le temps et les efforts consacrés à cette réponse. Bon travail!
GordyD
8
Bonne réponse, mais pensez à supprimer toutes les lignes "Modifier" et à restructurer un peu votre réponse. L'historique des modifications est disponible via le lien "édité ..." en bas de votre réponse, et il distrait pendant la lecture.
user247702
2
Le code source serait vraiment utile. Et si vous pouvez le rendre public sur npm, ce serait PARFAIT. Vraiment belle réponse, vraiment bien expliquée, +1 pour la quantité d'efforts consacrés à cela.
tfrascaroli
38

Dans la documentation pourangular.Module , il y a une entrée décrivant la runfonction:

Utilisez cette méthode pour enregistrer le travail qui doit être effectué lorsque l'injecteur a fini de charger tous les modules.

Donc, si vous avez un module qui est votre application:

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

Vous pouvez exécuter des choses une fois que les modules ont été chargés avec:

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

EDIT: Initialisation manuelle à la rescousse

Il a donc été souligné que le runn'est pas appelé lorsque le DOM est prêt et lié. Il est appelé lorsque le $injectorpour le module référencé par ng-appa 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:

<html>
    <body>
        <test-directive>This is a test</test-directive>
    </body>
</html>

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éé:

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

Et voici la directive:

app.directive('testDirective', function() {
    return {
        restrict: 'E',
        template: '<div class="test-directive"><h1><div ng-transclude></div></h1></div>',
        replace: true,
        transclude: true,
        compile: function() {
            console.log("Compiling test-directive");
            return {
                pre: function() { console.log("Prelink"); },
                post: function() { console.log("Postlink"); }
            };
        }
    };
});

Nous allons remplacer la test-directivebalise par un divof class test-directiveet envelopper son contenu dans un fichier h1.

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:

// 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;
}

Avant de faire quoi que ce soit, il ne devrait y avoir aucun élément avec une classe de test-directivedans le DOM, et une fois que nous avons terminé, il devrait y avoir 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'));
});

C'est assez simple. Lorsque le document est prêt, appelez angular.bootstrapavec l'élément racine de votre application et un tableau de noms de modules.

En fait, si vous attachez une runfonction au appmodule , 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:

before (should be 0): 0 
Compiling test-directive 
Prelink
Postlink
after (should be 1): 1 <--- success!
sacoche
la source
2
merci @satchmorun! mais run () s'exécute avant la fin de la partie de liaison - je viens de le vérifier avec certains console.logs.
Lior
j'étais curieux moi-même ... J'ai une directive qui se déclenche pour implémenter des plugins jQuery DOM, se rundéclenche avant la directive et lorsque l'exécution se déclenche, html n'est pas tout là
charlietfl
@charlietfl - J'ai creusé un peu dans le bootstrap manuel, et c'est en fait un moyen assez facile d'obtenir ce que la question recherche. J'ai ajouté une modification assez longue à ma réponse originale.
satchmorun
5
J'ai trouvé que l'utilisation de $timeout( initMyPlugins,0)travaux dans ma directive, tout le html dont j'ai besoin est là
charlietfl
@satchmorun, voir ce suivi: stackoverflow.com/questions/14989161/...
Lior
16

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.

Lior
la source
14

J'ai trouvé une solution relativement précise pour évaluer le moment où l'initialisation angulaire est terminée.

La directive est:

.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);
                    });
                }
            };
        }]);

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.

Théo
la source
Excellente solution. Pour les projets où tout le code dans un ou deux fichiers compressés fonctionne très bien.
merqlove
1
C'est une solution fantastique.Si vous avez beaucoup de code dans jquery et que vous essayez de convertir le code en angulaire étape par étape, cela est parfaitement logique.
Mangesh Pimpalkar
11

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

$scope.$on('$viewContentLoaded', 
function(event){ ... });
Jordan Skole
la source
3
$ scope. $ watch ('$ viewContentLoaded', function () a fait l'affaire pour moi
Louis XIV
2
a voté contre le «ce que vous devriez être». Et si je disais "si vous utilisez React au lieu d'Angular (ce que vous devriez être) ..."? Pas une attitude très à avoir dans cet écosystème à mon humble avis.
Valentin Waeselynck
@ValentinWaeselynck vous avez absolument raison. J'ai modifié ma réponse pour supprimer mon parti pris.
Jordan Skole
1
A travaillé pour moi! Je vous remercie. Je l'ai en fait ajouté à ma fonction d'exécution, puis j'ai changé $ scope en $ rootScope.
JDavies
2
Comme Angular University l'a souligné dans une réponse différente, $ viewContentLoaded n'était peut-être pas là à l'origine, mais il fonctionne maintenant dans le fournisseur ngRoute intégré, exactement de la même manière. Dans cet esprit, je pense que c'est la réponse rapide, simple et lisible que beaucoup (la plupart?) Futurs lecteurs rechercheront.
Kevin Crumley
3

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.

$(document).ready(function(){

  var interval = setInterval(function(){

  if($("article").size() == 7){
     myFunction();
     clearInterval(interval);
  }

  },50);

});
Hassan Gilak
la source
3
Je ne ferais pas ça. Utiliser des intervalles pour vérifier que des choses se produisent n'est pas une bonne pratique, n'est pas évolutif, et il existe d'autres moyens de faire bouger les choses. Les minuteries servent à accomplir des tâches concrètes qui doivent se produire après une certaine période de temps, et non à «deviner» quand le contenu ou les résultats sont prêts.
dudewad
Sans oublier que l'utilisation d'un minuteur jquery contre la plate-forme angulaire est contre-productive - angular a une classe de timeout et vous devriez l'utiliser sinon vous chevauchez deux frameworks et cela devient très rapidement déroutant.
dudewad
3

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:

    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");
                    }
                );
            }
        };
    }

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.

$ rootScope. $ emit déclenchera un événement pour tous les $ rootScope. $ sur les écouteurs uniquement. La partie intéressante est que $ rootScope. $ Broadcast notifiera tout $ rootScope. $ On ainsi que $ scope. $ Sur les écouteurs Source

Vadim P.
la source
2

Selon l'équipe Angular et ce problème Github :

nous avons maintenant les événements $ viewContentLoaded et $ includeContentLoaded qui sont émis respectivement dans ng-view et ng-include. Je pense que c'est aussi proche que possible de savoir quand nous en avons terminé avec la compilation.

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 :

En raison de plusieurs résumés, il est impossible de déterminer et d'avertir le composant que le modèle est stable. En effet, la notification peut modifier davantage les données, ce qui peut redémarrer le processus de liaison.

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.

Université angulaire
la source
2

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:

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

HTML du sous-partiel

<element ng-if="subIsLoaded()"><!-- more html --></element>
Hashbrown
la source
1

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.

module.factory('YourControllerInitService', function() {

    // add your initialization logic here

    // return empty service, because it will not be used
    return {};
});


module.controller('YourController', function (YourControllerInitService) {
});
Nikolay Georgiev
la source
0

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

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

})

Cliquez ici pour consulter la documentation complète - Crédit à K. Scott Allen

aholtry
la source
0

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

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

le $ compile renvoie des données compilées. vous pouvez vérifier les données compilées

shailendra pathak
la source