Événement d'arrivée ng-repeat

207

Je veux appeler une fonction jQuery ciblant div avec table. Ce tableau est rempli avec ng-repeat.

Quand je l'appelle

$(document).ready()

Je n'ai aucun résultat.

Aussi

$scope.$on('$viewContentLoaded', myFunc);

n'aide pas.

Existe-t-il un moyen d'exécuter la fonction juste après l'achèvement de la population ng-repeat? J'ai lu un conseil sur l'utilisation de la coutume directive, mais je ne sais pas comment l'utiliser avec ng-repeat et mon div ...

ChruS
la source

Réponses:

241

En effet, vous devez utiliser des directives, et il n'y a aucun événement lié à la fin d'une boucle ng-Repeat (car chaque élément est construit individuellement et a son propre événement). Mais a) l'utilisation de directives peut être tout ce dont vous avez besoin et b) il existe quelques propriétés spécifiques à ng-Repeat que vous pouvez utiliser pour rendre votre événement "on ngRepeat terminé".

Plus précisément, si tout ce que vous voulez est de styliser / ajouter des événements à l'ensemble du tableau, vous pouvez le faire en utilisant une directive qui englobe tous les éléments ngRepeat. D'un autre côté, si vous souhaitez traiter spécifiquement chaque élément, vous pouvez utiliser une directive dans le ngRepeat, et elle agira sur chaque élément, après sa création.

Ensuite, il y a les $index, $first, $middleet les $lastpropriétés que vous pouvez utiliser à des événements de déclenchement. Donc pour ce HTML:

<div ng-controller="Ctrl" my-main-directive>
  <div ng-repeat="thing in things" my-repeat-directive>
    thing {{thing}}
  </div>
</div>

Vous pouvez utiliser des directives comme ceci:

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('color','blue');
    if (scope.$last){
      window.alert("im the last!");
    }
  };
})
.directive('myMainDirective', function() {
  return function(scope, element, attrs) {
    angular.element(element).css('border','5px solid red');
  };
});

Voyez-le en action dans ce Plunker . J'espère que ça aide!

Tiago Roldão
la source
Puis-je faire myMainDirectiveexécuter le code de seulement après la fin de la boucle? J'ai besoin de mettre à jour la barre de défilement de la division parent.
ChruS
2
Bien sûr! Il suffit de changer le "window.alert" en une fonction de déclenchement d'événement et de l'attraper avec la directive principale. J'ai mis à jour de Plunker pour le faire, par exemple.
Tiago Roldão
9
Il est recommandé d'inclure la bibliothèque jQuery en premier (avant Angular). Cela entraînera que tous les éléments angulaires soient enveloppés avec jQuery (au lieu de jqLite). Ensuite, au lieu de angular.element (element) .css () et $ (element) .children (). Css (), vous pouvez simplement écrire element.css () et element.children (). Css (). Voir aussi groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok
11
Any1 avez une idée de comment faire fonctionner cela pour les rendus suivants sur la directive ngRepeat? ie: link
RavenHursT
1
Il s'agit d'une convention de dénomination pour toutes les directives angulaires. Voir docs.angularjs.org/guide/directive#normalization
Tiago Roldão
68

Si vous voulez simplement exécuter du code à la fin de la boucle, voici une variante légèrement plus simple qui ne nécessite pas de gestion supplémentaire des événements:

<div ng-controller="Ctrl">
  <div class="thing" ng-repeat="thing in things" my-post-repeat-directive>
    thing {{thing}}
  </div>
</div>
function Ctrl($scope) {
  $scope.things = [
    'A', 'B', 'C'  
  ];
}

angular.module('myApp', [])
.directive('myPostRepeatDirective', function() {
  return function(scope, element, attrs) {
    if (scope.$last){
      // iteration is complete, do whatever post-processing
      // is necessary
      element.parent().css('border', '1px solid black');
    }
  };
});

Voir une démo en direct.

karlgold
la source
Est-ce que cela fonctionne si j'utilise le contrôleur comme syntaxe? S'il vous plaît? Dans le cas contraire, existe-t-il un autre moyen qui fonctionne avec le contrôleur?
Dan Nguyen
63

Il n'est pas nécessaire de créer une directive, surtout pour avoir un ng-repeatévénement complet.

ng-init fait la magie pour vous.

  <div ng-repeat="thing in things" ng-init="$last && finished()">

le $lastfait que, ce finishedne se fera virer, lorsque le dernier élément a été rendu au DOM.

N'oubliez pas de créer un $scope.finishedévénement.

Codage heureux !!

EDIT: 23 oct.2016

Si vous souhaitez également appeler la finishedfonction lorsqu'il n'y a aucun élément dans le tableau, vous pouvez utiliser la solution de contournement suivante

<div style="display:none" ng-init="things.length < 1 && finished()"></div>
//or
<div ng-if="things.length > 0" ng-init="finished()"></div>

Ajoutez simplement la ligne ci-dessus en haut de la ng-repeat élément. Il vérifiera si le tableau n'a aucune valeur et appellera la fonction en conséquence.

Par exemple

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">
Vikas Bansal
la source
Et si les "choses" s'avéraient être un tableau vide?
Frane Poljak
1
@FranePoljak J'ai ajouté la solution dans la réponse.
Vikas Bansal du
1
En cas de baie vide, manipulez-la dans le contrôleur
JSAddict
Correction de la réponse de Vikas Bansal: // utilisez la première div si vous voulez vérifier si le tableau n'a aucune valeur et appelez la fonction en conséquence. <div ng-if = "! things.length" ng-hide = "true" ng-init = "done ()"> </div> <div ng-repeat = "chose dans les choses" ng-init = "$ dernier && terminé () ">
Alex Teslenko
41

Voici une directive répétée qui appelle une fonction spécifiée lorsqu'elle est vraie. J'ai trouvé que la fonction appelée doit utiliser $ timeout avec interval = 0 avant de faire une manipulation DOM, comme l'initialisation des info-bulles sur les éléments rendus. jsFiddle: http://jsfiddle.net/tQw6w/

Dans $ scope.layoutDone, essayez de commenter la ligne $ timeout et de décommenter le "NOT CORRECT!" pour voir la différence dans les info-bulles.

<ul>
    <li ng-repeat="feed in feedList" repeat-done="layoutDone()" ng-cloak>
    <a href="{{feed}}" title="view at {{feed | hostName}}" data-toggle="tooltip">{{feed | strip_http}}</a>
    </li>
</ul>

JS:

angular.module('Repeat_Demo', [])

    .directive('repeatDone', function() {
        return function(scope, element, attrs) {
            if (scope.$last) { // all are rendered
                scope.$eval(attrs.repeatDone);
            }
        }
    })

    .filter('strip_http', function() {
        return function(str) {
            var http = "http://";
            return (str.indexOf(http) == 0) ? str.substr(http.length) : str;
        }
    })

    .filter('hostName', function() {
        return function(str) {
            var urlParser = document.createElement('a');
            urlParser.href = str;
            return urlParser.hostname;
        }
    })

    .controller('AppCtrl', function($scope, $timeout) {

        $scope.feedList = [
            'http://feeds.feedburner.com/TEDTalks_video',
            'http://feeds.nationalgeographic.com/ng/photography/photo-of-the-day/',
            'http://sfbay.craigslist.org/eng/index.rss',
            'http://www.slate.com/blogs/trending.fulltext.all.10.rss',
            'http://feeds.current.com/homepage/en_US.rss',
            'http://feeds.current.com/items/popular.rss',
            'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
        ];

        $scope.layoutDone = function() {
            //$('a[data-toggle="tooltip"]').tooltip(); // NOT CORRECT!
            $timeout(function() { $('a[data-toggle="tooltip"]').tooltip(); }, 0); // wait...
        }

    })
Joseph Oster
la source
Je ne voulais pas utiliser un timeout $, mais quand vous avez dit qu'il pouvait être mis à 0, j'ai décidé de l'essayer et cela a bien fonctionné. Je me demande si c'est une question de digestion, et s'il existe un moyen de faire ce que fait $ timeout sans utiliser $ timeout.
Jameela Huq
Pouvez-vous expliquer pourquoi cela fonctionne? J'essaie d'accéder aux ID dynamiques et cela ne fonctionne qu'après un délai d'attente avec 0 retard. J'ai vu cette solution "hacky" dans plusieurs articles mais personne n'explique pourquoi cela fonctionne.
Jin
30

Voici une approche simple ng-initqui ne nécessite même pas de directive personnalisée. Cela a bien fonctionné pour moi dans certains scénarios, par exemple avoir besoin de faire défiler automatiquement une division d'éléments ng répétés vers un élément particulier lors du chargement de la page, de sorte que la fonction de défilement doit attendre jusqu'à ce que le ng-repeatrendu soit terminé sur le DOM avant de pouvoir se déclencher.

<div ng-controller="MyCtrl">
    <div ng-repeat="thing in things">
        thing: {{ thing }}
    </div>
    <div ng-init="fireEvent()"></div>
</div>

myModule.controller('MyCtrl', function($scope, $timeout){
    $scope.things = ['A', 'B', 'C'];

    $scope.fireEvent = function(){

        // This will only run after the ng-repeat has rendered its things to the DOM
        $timeout(function(){
            $scope.$broadcast('thingsRendered');
        }, 0);

    };
});

Notez que cela n'est utile que pour les fonctions que vous devez appeler une fois après le rendu initial de la répétition ng. Si vous devez appeler une fonction chaque fois que le contenu ng-repeat est mis à jour, vous devrez utiliser l'une des autres réponses sur ce fil avec une directive personnalisée.

dshap
la source
1
Magnifique, cela a fait le travail. Je voulais sélectionner automatiquement le texte dans une zone de texte, et le délai d'attente a fait l'affaire. Sinon, le texte {{model.value}} a été sélectionné, puis désélectionné lorsque le model.value lié aux données a été injecté.
JoshGough
27

En complément de la réponse de Pavel , quelque chose de plus lisible et facilement compréhensible serait:

<ul>
    <li ng-repeat="item in items" 
        ng-init="$last ? doSomething() : angular.noop()">{{item}}</li>
</ul>

Sinon, pourquoi pensez-vous angular.noop y a d'abord ...?

Avantages:

Vous n'avez pas besoin d'écrire une directive pour cela ...

déostroll
la source
20
Pourquoi utiliser un ternaire quand vous pouvez utiliser la logique booléenne:ng-init="$last && doSomething( )"
Nate Whittaker
C'est plus facile à lire @NateWhittaker (et plus difficile à lire qu'un opérateur ternaire est impressionnant). C'est bon d'avoir du code propre et simple à comprendre
Justin
21

Peut-être une approche un peu plus simple avec ngInitLodash etdebounce méthode sans avoir besoin de directive personnalisée:

Manette:

$scope.items = [1, 2, 3, 4];

$scope.refresh = _.debounce(function() {
    // Debounce has timeout and prevents multiple calls, so this will be called 
    // once the iteration finishes
    console.log('we are done');
}, 0);

Modèle:

<ul>
    <li ng-repeat="item in items" ng-init="refresh()">{{item}}</li>
</ul>

Mettre à jour

Il existe une solution AngularJS pure encore plus simple utilisant l'opérateur ternaire:

Modèle:

<ul>
    <li ng-repeat="item in items" ng-init="$last ? doSomething() : null">{{item}}</li>
</ul>

Sachez que ngInit utilise la phase de compilation de pré-liaison - c'est-à-dire que l'expression est invoquée avant le traitement des directives enfants. Cela signifie qu'un traitement asynchrone peut encore être requis.

Pavel Horal
la source
Doit noter que vous avez besoin du lodash.js pour que cela fonctionne :) Aussi: la partie ", 20" du $ scope.refresh =, est ce nombre de millis avant que cette méthode soit appelée après la dernière itération?
Jørgen Skår Fischer
Ajout de Lodash devant le lien anti - rebond . Oui, 20 est un délai avant l'exécution de la méthode - changé à 0 car cela n'a pas d'importance tant que l'appel est asynchrone.
Pavel Horal
1
En pensant également à cela, comme l'opérateur ternaire est autorisé dans les expressions simples, ng-init="$last ? refresh() : false"cela fonctionnerait aussi. Et cela ne nécessite même pas Lodash.
Pavel Horal
<div ng-repeat = "i in [1,2,4]" ng-init = "doSomething ($ last)"> </div> $ scope.doSomething = function (lastElement) {if (lastElem) bla bla bla bla }
JSAddict
4

Il peut également être nécessaire lorsque vous cochez la scope.$lastvariable d'envelopper votre déclencheur avec a setTimeout(someFn, 0). A setTimeout 0est une technique acceptée en javascript et il était impératif que ma directivefonctionne correctement.

Cody
la source
j'ai trouvé le même problème. à la fois dans backbone + angular, si vous devez faire quelque chose comme lire les valeurs de propriété css appliquées à un élément DOM, je ne pouvais pas trouver un moyen sans le piratage du délai d'expiration.
chovy
3

Il s'agit d'une amélioration des idées exprimées dans d'autres réponses afin de montrer comment accéder aux propriétés ngRepeat ($ index, $ first, $ middle, $ last, $ even, $ odd) lors de l'utilisation de la syntaxe déclarative et de l'isolement de la portée ( Google a recommandé les meilleures pratiques) avec une directive sur les éléments. Notez la différence principale: scope.$parent.$last.

angular.module('myApp', [])
.directive('myRepeatDirective', function() {
  return {
    restrict: 'E',
    scope: {
      someAttr: '='
    },
    link: function(scope, element, attrs) {
      angular.element(element).css('color','blue');
      if (scope.$parent.$last){
        window.alert("im the last!");
      }
    }
  };
});
JoshuaDavid
la source
Ne serait-il pas préférable d'utiliser des directives comme attributs que comme éléments? c'est-à-dire restreindre: «A»?
Tejasvi Hegde
2
Le but était de montrer la syntaxe déclarative + la portée de l'isolat ... oui, vous pouvez utiliser ici une directive d'attribut si vous le souhaitez. Il y a un temps et un lieu pour chacun selon votre objectif.
JoshuaDavid
3

Je l'ai fait de cette façon.

Créer la directive

function finRepeat() {
    return function(scope, element, attrs) {
        if (scope.$last){
            // Here is where already executes the jquery
            $(document).ready(function(){
                $('.materialboxed').materialbox();
                $('.tooltipped').tooltip({delay: 50});
            });
        }
    }
}

angular
    .module("app")
    .directive("finRepeat", finRepeat);

Après l'avoir ajouté sur l'étiquette où cette ng-répétition

<ul>
    <li ng-repeat="(key, value) in data" fin-repeat> {{ value }} </li>
</ul>

Et prêt avec cela sera exécuté à la fin de la répétition ng.

Mvochoa
la source
3
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

Ma solution était d'ajouter un div pour appeler une fonction si l'élément était le dernier d'une répétition.

scotty3
la source
2

je voudrais ajouter une autre réponse, car les réponses précédentes supposent que le code nécessaire pour s'exécuter après la fin de ngRepeat est un code angulaire, qui dans ce cas toutes les réponses ci-dessus donnent une solution simple et excellente, certaines plus génériques que d'autres, et dans le cas où il est important de l'étape du cycle de vie de résumé, vous pouvez jeter un oeil à le blog de Ben Nadel à ce sujet, à l'exception de l'utilisation de $ parse au lieu de $ eval.

mais d'après mon expérience, comme le déclare l'OP, il exécute généralement des plugins ou des méthodes JQuery sur le DOM compilé finlandais, ce qui dans ce cas, j'ai trouvé que la solution la plus simple consiste à créer une directive avec un setTimeout, car la fonction setTimeout est poussée à la fin de la file d'attente du navigateur, c'est toujours juste après que tout soit fait en angulaire, généralement ngReapet qui continue après la fonction postLinking de ses parents

angular.module('myApp', [])
.directive('pluginNameOrWhatever', function() {
  return function(scope, element, attrs) {        
    setTimeout(function doWork(){
      //jquery code and plugins
    }, 0);        
  };
});

pour ceux qui se demandent que dans ce cas pourquoi ne pas utiliser $ timeout, c'est que cela provoque un autre cycle de digestion qui est complètement inutile

bresleveloper
la source
1

J'ai dû rendre des formules en utilisant MathJax après la fin de ng-repeat, aucune des réponses ci-dessus n'a résolu mon problème, alors j'ai fait comme ci-dessous. Ce n'est pas une bonne solution , mais ça a marché pour moi ...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>
João Paulo
la source
0

J'ai trouvé une réponse ici bien pratiquée, mais il fallait quand même ajouter un délai

Créez la directive suivante:

angular.module('MyApp').directive('emitLastRepeaterElement', function() {
return function(scope) {
    if (scope.$last){
        scope.$emit('LastRepeaterElement');
    }
}; });

Ajoutez-le à votre répéteur en tant qu'attribut, comme ceci:

<div ng-repeat="item in items" emit-last-repeater-element></div>

Selon Radu:

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {mycode});

Pour moi, cela fonctionne, mais je dois encore ajouter un setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });
Blomersi
la source
-4

Si vous voulez simplement changer le nom de la classe pour qu'il soit rendu différemment, le code ci-dessous ferait l'affaire.

<div>
<div ng-show="loginsuccess" ng-repeat="i in itemList">
    <div id="{{i.status}}" class="{{i.status}}">
        <div class="listitems">{{i.item}}</div>
        <div class="listitems">{{i.qty}}</div>
        <div class="listitems">{{i.date}}</div>
        <div class="listbutton">
            <button ng-click="UpdateStatus(i.$id)" class="btn"><span>Done</span></button>
            <button ng-click="changeClass()" class="btn"><span>Remove</span></button>
        </div>
    <hr>
</div>

Ce code a fonctionné pour moi lorsque j'avais une exigence similaire pour rendre l'élément acheté dans ma liste d'achats en police Strick Trough.

user4808836
la source