Appeler une fonction lorsque ng-repeat est terminé

Ce que j’essaie d’implémenter est fondamentalement un gestionnaire “on ng repeat finish rendering”. Je suis capable de détecter quand c’est fait mais je ne peux pas comprendre comment déclencher une fonction à partir de cela.

Vérifiez le violon: http://jsfiddle.net/paulocoelho/BsMqq/3/

JS

var module = angular.module('testApp', []) .directive('onFinishRender', function () { return { ressortingct: 'A', link: function (scope, element, attr) { if (scope.$last === true) { element.ready(function () { console.log("calling:"+attr.onFinishRender); // CALL TEST HERE! }); } } } }); function myC($scope) { $scope.ta = [1, 2, 3, 4, 5, 6]; function test() { console.log("test executed"); } } 

HTML

 

{{t}}

Réponse : Travailler violon de la finition: http://jsfiddle.net/paulocoelho/BsMqq/4/

 var module = angular.module('testApp', []) .directive('onFinishRender', function ($timeout) { return { ressortingct: 'A', link: function (scope, element, attr) { if (scope.$last === true) { $timeout(function () { scope.$emit(attr.onFinishRender); }); } } } }); 

Notez que je n’ai pas utilisé .ready() mais que je l’ai plutôt inclus dans un $timeout . $timeout s’assure qu’il est exécuté lorsque les éléments répétés ng ont VRAIMENT fini de rendre (car le $timeout s’exécutera à la fin du cycle de digestion en cours – et il appellera également $apply interne, contrairement à setTimeout ). Donc, une fois que ng-repeat est terminé, nous utilisons $emit pour émettre un événement vers des étendues externes (étendues frères et parents).

Et puis dans votre contrôleur, vous pouvez l’attraper avec $on :

 $scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) { //you also get the actual event object //do stuff, execute functions -- whatever... }); 

Avec HTML, cela ressemble à ceci:

 
{{item.name}}}

Utilisez $ evalAsync si vous souhaitez que votre callback (c’est-à-dire test ()) soit exécuté après la construction du DOM, mais avant que le navigateur ne soit rendu. Cela évitera le scintillement – réf .

 if (scope.$last) { scope.$evalAsync(attr.onFinishRender); } 

Le violon

Si vous voulez vraiment appeler votre rappel après le rendu, utilisez $ timeout:

 if (scope.$last) { $timeout(function() { scope.$eval(attr.onFinishRender); }); } 

Je préfère $ eval au lieu d’un événement. Avec un événement, nous devons connaître le nom de l’événement et append du code à notre contrôleur pour cet événement. Avec $ eval, il y a moins de couplage entre le contrôleur et la directive.

Les réponses qui ont été données jusqu’ici ne fonctionneront que la première fois que ng-repeat rendu , mais si vous avez une ng-repeat dynamic, ce qui signifie que vous allez append / supprimer / filtrer des éléments, et que vous devez être averti chaque fois que le ng-repeat est rendu, ces solutions ne fonctionneront pas pour vous.

Donc, si vous avez besoin d’être averti chaque fois que le ng-repeat est re-rendu et pas seulement la première fois, j’ai trouvé un moyen de le faire, c’est assez “pirate”, mais ça fonctionnera très bien si vous savez que fais tu. Utilisez ce $filter dans votre ng-repeat avant d’utiliser un autre $filter :

 .filter('ngRepeatFinish', function($timeout){ return function(data){ var me = this; var flagProperty = '__finishedRendering__'; if(!data[flagProperty]){ Object.defineProperty( data, flagProperty, {enumerable:false, configurable:true, writable: false, value:{}}); $timeout(function(){ delete data[flagProperty]; me.$emit('ngRepeatFinished'); },0,false); } return data; }; }) 

Cela $emit un événement appelé ngRepeatFinished chaque fois que ng-repeat rendu.

Comment l’utiliser:

 
  • Le filtre ngRepeatFinish doit être appliqué directement à un Array ou à un Object défini dans votre $scope , vous pouvez appliquer ensuite d’autres filtres.

    Comment ne pas l’utiliser:

     
  • N’appliquez pas d’abord d’autres filtres, puis appliquez le filtre ngRepeatFinish .

    Quand devrais-je l’utiliser?

    Si vous voulez appliquer certains styles de CSS dans le DOM après le rendu de la liste, car vous devez tenir compte des nouvelles dimensions des éléments DOM qui ont été restitués par le ng-repeat . (BTW: ce type d’opérations doit être effectué dans une directive)

    Que ne pas faire dans la fonction qui gère l’événement ngRepeatFinished :

    • N’effectuez pas de $scope.$apply dans cette fonction ou vous placerez Angular dans une boucle sans fin que Angular ne pourra pas détecter.

    • Ne l’utilisez pas pour apporter des modifications aux propriétés $scope , car ces modifications ne seront pas reflétées dans votre vue avant la prochaine boucle $digest , et comme vous ne pouvez pas effectuer une $scope.$apply toute utilisation.

    “Mais les filtres ne sont pas destinés à être utilisés comme ça !!”

    Non, ils ne sont pas, c’est un hack, si vous n’aimez pas ça ne l’utilise pas. Si vous connaissez une meilleure façon d’accomplir la même chose, faites-le moi savoir.

    Résumer

    Ceci est un hack , et l’utiliser de manière incorrecte est dangereux, utilisez-le uniquement pour appliquer des styles une fois le rendu de ng-repeat terminé et vous ne devriez plus avoir de problèmes.

    Si vous devez appeler différentes fonctions pour différentes répétitions ng sur le même contrôleur, vous pouvez essayer quelque chose comme ceci:

    La directive:

     var module = angular.module('testApp', []) .directive('onFinishRender', function ($timeout) { return { ressortingct: 'A', link: function (scope, element, attr) { if (scope.$last === true) { $timeout(function () { scope.$emit(attr.broadcasteventname ? attr.broadcasteventname : 'ngRepeatFinished'); }); } } } }); 

    Dans votre contrôleur, interceptez les événements avec $ on:

     $scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) { // Do something }); $scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) { // Do something }); 

    Dans votre template avec plusieurs ng-repeat

     
    {{item.name}}}
    {{item.name}}}

    Les autres solutions fonctionneront correctement lors du chargement initial de la page, mais l’appel de $ timeout depuis le contrôleur est le seul moyen de s’assurer que votre fonction est appelée lorsque le modèle change. Voici un violon de travail qui utilise $ timeout. Pour votre exemple, ce serait:

     .controller('myC', function ($scope, $timeout) { $scope.$watch("ta", function (newValue, oldValue) { $timeout(function () { test(); }); }); 

    ngRepeat n’évaluera une directive que lorsque le contenu de la ligne est nouveau, donc si vous supprimez des éléments de votre liste, onFinishRender ne se déclenche pas. Par exemple, essayez d’entrer des valeurs de filtre dans ces fiddles émis .

    Si vous n’êtes pas opposé à l’utilisation des accessoires à double dollar et que vous écrivez une directive dont le seul contenu est une répétition, il existe une solution assez simple (en supposant que vous ne vous souciez que du rendu initial). Dans la fonction de lien:

     const dereg = scope.$watch('$$childTail.$last', last => { if (last) { dereg(); // do yr stuff -- you may still need a $timeout here } }); 

    Ceci est utile dans les cas où vous avez une directive qui doit faire de la manipulation DOM en fonction de la largeur ou de la hauteur des membres d’une liste rendue (ce qui est la raison la plus probable pour laquelle cette question est posée), comme les autres solutions qui ont été proposées.

    Une solution à ce problème avec un ngRepeat filtré aurait pu être avec les événements de mutation , mais ils sont obsolètes (sans remplacement immédiat).

    Puis j’ai pensé à un autre facile:

     app.directive('filtered',function($timeout) { return { ressortingct: 'A',link: function (scope,element,attr) { var elm = element[0] ,nodePrototype = Node.prototype ,timeout ,slice = Array.prototype.slice ; elm.insertBefore = alt.bind(null,nodePrototype.insertBefore); elm.removeChild = alt.bind(null,nodePrototype.removeChild); function alt(fn){ fn.apply(elm,slice.call(arguments,1)); timeout&&$timeout.cancel(timeout); timeout = $timeout(altDone); } function altDone(){ timeout = null; console.log('Filtered! ...fire an event or something'); } } }; }); 

    Cela raccorde les méthodes Node.prototype de l’élément parent avec un délai d’expiration à un tick pour surveiller les modifications successives.

    Cela fonctionne principalement correct mais j’ai eu quelques cas où le altDone serait appelé deux fois.

    Encore une fois … ajoutez cette directive au parent du ngRepeat.

    Très facile, c’est comme ça que je l’ai fait.

     .directive('blockOnRender', function ($blockUI) { return { ressortingct: 'A', link: function (scope, element, attrs) { if (scope.$first) { $blockUI.blockElement($(element).parent()); } if (scope.$last) { $blockUI.unblockElement($(element).parent()); } } }; }) 

    Veuillez regarder le violon, http://jsfiddle.net/yNXS2/ . Depuis que la directive que vous avez créée n’a pas créé un nouveau périmètre, j’ai continué sur ma lancée.

    $scope.test = function(){... rendu cela possible.