117 votes

Envoi d’événement lors de la fin du chargement de angular.js

Je me demandais quel est le meilleur moyen pour détecter l’arrivée de la page chargement/amorçage, quand toutes les directives faits compilations/liens

tout événement déjà là ? Je devrais surcharger la fonction « bootstrap » ?

Merci pour toutes les idées Lior

209voto

trusktr Points 4518

Juste une idée: pourquoi ne pas regarder la façon dont les ngCloak la directive est-il? Clairement le ngCloak directive parvient à afficher le contenu après que les choses ont chargé. Je parie que en regardant ngCloak conduira à 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, bien entendu, est que la compilation de la fonction ne sera pas exécutée jusqu'à ce que {{modèle}} les expressions ont été évalués (c'est à dire le modèle, il chargé), ainsi que la fonctionnalité de nice le ngCloak directive.

Mon hypothèse serait de simplement faire une directive avec la même simplicité d'ngCloak, puis dans votre compiler la fonction de faire ce que vous voulez faire. :) Place de la directive sur l'élément racine de votre application. Vous pouvez appeler la directive quelque chose comme myOnload et l'utiliser comme un attribut de ma-onload. La compilation en fonction ne s'exécute une fois que le modèle a été compilé (expressions évaluées et sous-modèles chargé).

EDIT, 23 heures plus tard: Ok, j'ai donc fait quelques recherches, et j'ai également demandé à ma propre question. La question que j'ai posée était indirectement liés à cette question, mais il coïncidence me conduire à la réponse qui résout cette question.

La réponse est que vous pouvez créer une simple directive et de mettre votre code dans la directive de la fonction de lien, ce qui (pour la plupart des cas, expliqué ci-dessous) sera exécuté lorsque votre élément est prêt/chargé. Basé sur Josh, la description de l'ordre dans lequel les compiler et lier les fonctions sont exécutées,

si vous avez cette balise:

<div directive1>
  <div directive2>
    <!-- ... -->
  </div>
</div>

Ensuite, AngularJS va créer les directives en cours d'exécution de la directive fonctions 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 droite "lien" de la fonction est un post-lien, de sorte que votre extérieur directive1 relative de la fonction de lien ne fonctionnera pas jusqu'à ce que après l'intérieure directive2 de la fonction de lien a couru. C'est pourquoi nous disons que c'est seulement pas de danger de manipulation du DOM dans le post-link. Vers l'original question, il devrait y avoir aucun problème d'accès à l'enfant de la directive intérieure html à partir de l'extérieur de la directive en fonction de lien, si insérés dynamiquement le contenu doit être compilé, comme dit ci-dessus.

De cela, nous pouvons conclure que l'on peut se contenter de faire une directive pour exécuter notre code quand tout est prêt/compilé/lien/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 ngElementReady directive sur l'élément racine de l'application, et l' console.log le feu quand il est chargé:

<body data-ng-app="MyApp" data-ng-element-ready="">
   ...
   ...
</body>

C'est très simple! Il suffit d'une simple directive et de l'utiliser. ;)

Vous pouvez le personnaliser de sorte qu'il peut exécuter une expression (c'est à dire une fonction) par l'ajout d' $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 d'avoir vos fonctions (par exemple, bodyIsReady et divIsReady) défini dans le champ d'application (le contrôleur) que votre élément de vie.

Mises en garde: j'ai dit que cela fonctionnera pour la plupart des cas. Être prudent lors de l'utilisation de certaines directives, comme ngRepeat et ngIf. Ils créent leur propre champ d'application, et votre directive ne peut pas le feu. Par exemple, si vous avez mis notre nouveau ngElementReady directive sur un élément qui a également ngIf, et la condition de la ngIf évalue à false, alors notre ngElementReady la directive ne sera pas chargée. Ou, par exemple, si vous avez mis notre nouveau ngElementReady directive sur un élément qui a aussi un ngInclude directive, la directive ne sera pas chargé si le modèle du ngInclude n'existe pas. Vous pouvez obtenir autour de certains de ces problèmes en vous assurant de nest les directives au lieu de les mettre tous 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>

Le ngElementReady directive seront compilés dans le dernier exemple, mais c'est fonction de lien ne sera pas exécuté. Remarque: les directives sont toujours compilé, mais le lien avec leurs fonctions 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 d'attribut. :) E. g.:

<div data-ng-element-ready="$emit('someEvent')">
    ...
<div>

MODIFIER, même plus, quelques minutes plus tard:

@satchmorun réponse fonctionne aussi, mais seulement pour la charge initiale. Voici un très utile DONC, la question qui décrit l'ordre des choses sont exécutés, y compris les fonctions de liaison, app.run, et les autres. Donc, selon votre cas d'utilisation, app.run pourrait être bon, mais pas pour des éléments spécifiques, auquel cas les fonctions de liaison sont mieux.

EDIT, cinq mois plus tard, le 17 Oct à 8:11 PST:

Cela ne fonctionne pas avec les partiels qui sont chargés de manière asynchrone. Vous aurez besoin d'ajouter de comptabilité dans votre partiels (par exemple, une façon est de faire de chaque partiel de garder une trace de quand son contenu est fait, le chargement puis émet un événement de sorte que le parent peut compter le nombre de partiels ont chargé et enfin faire ce qu'il doit faire après tous les partiels sont chargés).

EDIT, le 23 octobre à 10:52pm PST:

J'ai fait une simple directive pour la cuisson de certains de code lorsque l'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 Oct à 12:48 PST:

J'ai amélioré mon originale ngElementReady directive et le renomme en 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, l'utiliser comme ceci à feu 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ée avant tout l' item.property des espaces réservés sont remplacés.

Évaluer autant d'expressions que vous voulez, et de faire la dernière expression en true attendre {{placeholders}} à être évalué comme suit:

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

someFunction et anotherFunction sera déclenché après l' {{placeholders}} ont été remplacés.

Cela ne marche que la première fois qu'un élément est chargé, pas sur les évolutions futures. Il peut ne fonctionne pas comme souhaité si un $digest continue à se produire après des espaces réservés ont d'abord été remplacé ($digest peut se produire jusqu'à 10 fois jusqu'à ce que les données de cesse de changer). Il va être adapté pour une grande majorité de cas d'utilisation.

EDIT, 31 Oct à 7:26pm PST:

Bon, c'est probablement ma dernière mise à jour. Ce sera sans doute 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: http://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 (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);
      }
    }
  };
}]);

L'utiliser comme ceci

<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 en rester là. requestAnimationFrame est agréable.

38voto

satchmorun Points 7504

Dans les docs pour angular.Module, il y a une entrée décrivant l' run fonction de:

Utiliser cette méthode pour enregistrer le travail qui doit être effectué lorsque l'injecteur est fait, le chargement de tous les modules.

Donc, si vous avez quelques module de votre application:

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

Vous pouvez exécuter des trucs après que les modules ont chargé de:

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

EDIT: Manuel de l'Initialisation à la rescousse

Ainsi, il a été souligné que l' run n'est pas appelée lorsque le DOM est prêt et relié. Elle est appelée lorsque l' $injector pour le module référencé par ng-app a chargé tous ses dépendances, qui est distinct de la DOM étape de compilation.

J'ai pris un autre regard sur l'initialisation manuelle, et il semble que cela devrait faire l'affaire.

J'ai fait du violon pour illustrer.

Le HTML est simple:

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

Note de l'absence d'un ng-app. Et j'ai une directive qui vont faire de la manipulation du DOM, de sorte que nous pouvons nous assurer de l'ordre et le calendrier de 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 l' test-directive balise avec un div de la classe test-directive, et l'envelopper de son contenu dans une h1.

J'ai ajouté une compilation fonction qui renvoie à la fois pré-et post-fonctions de liaison afin que nous puissions voir si ces choses se passent.

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 que nous ayons fait quoi que ce soit, il ne devrait pas être des éléments avec une classe de test-directive dans les DOM, et après que nous aurons terminé, il devrait être de 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.bootstrap avec l'élément racine de votre application et d'un tableau de noms de modules.

En fait, si vous attachez une run fonction de l' app module, vous verrez qu'il est exécuté avant tout de la compilation a lieu.

Si vous exécutez le violon, et de regarder la console, vous verrez suivantes:

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

17voto

Lior Points 6542

Voulais ajouter une réalisation que j'ai eu sur ma propre question: Anguleuse n'en a pas fourni un moyen de signal lorsqu'une page fini de charger, peut-être parce que "fini" dépend de votre application. par exemple, si vous avez un arbre hiérarchique de partiels, un chargement les autres. "fini" signifie que tous d'entre eux ont été chargés. Le cadre aurait un moment difficile l'analyse de votre code et de la compréhension que tout est fait, ou encore attendu.Pour cela, vous devez fournir spécifiques à l'application de la logique à vérifier et à le déterminer.

merci

Lior

15voto

Theo Points 550

Je suis venu avec une solution qui est relativement fidèle à l'évaluation lorsque l'angle d'initialisation 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);
                    });
                }
            };
        }]);

Qui peut donc être ajouté comme un attribut de l' body élément et ensuite écouté pour l'utilisation de $scope.$on('initialised', fn)

Il fonctionne en supposant que l'application démarre, quand il n'y a pas plus de $digest cycles. $montre est appelée à chaque digérer cycle et donc un timer est démarré (setTimeout pas $timeout donc une nouvelle digérer le cycle n'est pas déclenchée). Si un condensé de cycle ne se produit pas dans le délai, la demande est réputée avoir été initialisée.

Il n'est évidemment pas aussi précis que satchmoruns solution (comme il est possible qu'un digest cycle dure plus longtemps que le délai d'attente), mais ma solution n'a pas besoin de vous pour garder la trace des modules qui le rend beaucoup plus facile à gérer (en particulier pour les grands projets). De toute façon, semble être assez précis pour mes besoins. Espérons que cela aide.

3voto

Hasan Points 21

J'observe les manipulations DOM angulaires avec JQuery et j'ai fixé la finition de 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 répéteur ng produise 7 résultats et définira une fonction d’observation à l’aide de setInterval à cette fin.

 $(document).ready(function(){

  var interval = setInterval(function(){

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

  },50);

});
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X