Evento finale ng-repeat


207

Voglio chiamare alcune funzioni jQuery destinate al div con tabella. Quella tabella è popolata conng-repeat .

Quando lo chiamo

$(document).ready()

Non ho risultato

Anche

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

non aiuta.

Esiste un modo per eseguire la funzione subito dopo il completamento della popolazione ng-repeat? Ho letto un consiglio sull'uso di custom directive, ma non ho idea di come usarlo con ng-repeat e il mio div ...

Risposte:


241

In effetti, dovresti usare le direttive e non c'è nessun evento legato alla fine di un ciclo ng-Repeat (poiché ogni elemento è costruito individualmente e ha il suo evento). Ma a) usare le direttive potrebbe essere tutto ciò di cui hai bisogno eb) ci sono alcune proprietà specifiche di ng-Repeat che puoi usare per rendere l'evento "on ngRepeat finito".

In particolare, se tutto ciò che vuoi è stile / aggiungere eventi all'intera tabella, puoi farlo usando una direttiva che comprende tutti gli elementi ngRepeat. D'altra parte, se si desidera indirizzare in modo specifico ciascun elemento, è possibile utilizzare una direttiva all'interno di ngRepeat, che agirà su ciascun elemento, dopo che è stato creato.

Poi, ci sono il $index, $first, $middlee $lastle proprietà che si possono utilizzare per eventi di trigger. Quindi per questo HTML:

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

Puoi usare le direttive in questo modo:

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

Guardalo in azione in questo Plunker . Spero che sia d'aiuto!


Posso far myMainDirectiveeseguire il codice solo dopo la fine del ciclo? Devo aggiornare la barra di scorrimento del div genitore.
ChruS,

2
Ovviamente! Basta cambiare "window.alert" in una funzione di attivazione di eventi e catturarlo con la direttiva principale. Ho aggiornato de Plunker per fare questo, come esempio.
Tiago Roldão,

9
Si consiglia di includere prima la libreria jQuery (prima di Angular). Ciò causerà il wrapping di tutti gli elementi angolari con jQuery (anziché jqLite). Quindi invece di angular.element (element) .css () e $ (element) .children (). Css () puoi semplicemente scrivere element.css () e element.children (). Css (). Vedi anche groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok

11
Qualcuno1 ha idea di come farlo funzionare per i successivi rendering sulla direttiva ngRepeat? cioè: link
RavenHursT

1
Questa è una convenzione di denominazione per tutte le direttive angolari. Vedi docs.angularjs.org/guide/directive#normalization
Tiago Roldão

68

Se vuoi semplicemente eseguire un po 'di codice alla fine del ciclo, ecco una variazione leggermente più semplice che non richiede una gestione extra degli eventi:

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

Guarda una demo dal vivo.


Funziona se utilizzo il controller come sintassi? Per favore? In non, c'è qualche altro modo che funziona con il controller come?
Dan Nguyen,

63

Non è necessario creare una direttiva, soprattutto solo per avere un ng-repeatevento completo.

ng-init fa la magia per te.

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

si $lastassicura che finishedvenga attivato solo quando l'ultimo elemento è stato renderizzato sul DOM.

Non dimenticare di creare un $scope.finishedevento.

Buona programmazione !!

EDIT: 23 ott 2016

Nel caso in cui si desideri chiamare anche la finishedfunzione quando non vi sono elementi nell'array, è possibile utilizzare la seguente soluzione alternativa

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

Aggiungi semplicemente la riga sopra nella parte superiore ng-repeatdell'elemento. Verificherà se l'array non ha alcun valore e chiamerà la funzione di conseguenza.

Per esempio

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">

E se le "cose" risultassero essere un array vuoto?
Frane Poljak,

1
@FranePoljak Ho aggiunto la soluzione nella risposta.
Vikas Bansal,

1
In caso di array vuoto, gestirlo nel controller
JSAddict

Risolvi la risposta di Vikas Bansal: // usa il primo div se vuoi controllare se l'array non ha alcun valore e chiama la funzione di conseguenza. <div ng-if = "! things.length" ng-hide = "true" ng-init = "finito ()"> </div> <div ng-repeat = "cosa nelle cose" ng-init = "$ last && finish () ">
Alex Teslenko,

41

Ecco una direttiva ripetuta che chiama una funzione specificata quando è vera. Ho scoperto che la funzione chiamata deve utilizzare $ timeout con intervallo = 0 prima di eseguire la manipolazione DOM, come l'inizializzazione di descrizioni comandi sugli elementi renderizzati. jsFiddle: http://jsfiddle.net/tQw6w/

In $ scope.layoutDone, prova a commentare la riga $ timeout e a rimuovere il commento da "NON CORRETTO!" linea per vedere la differenza nelle descrizioni comandi.

<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...
        }

    })

Non volevo usare un timeout $, ma quando hai detto che poteva essere impostato su 0, ho deciso di provarlo e ha funzionato bene. Mi chiedo se questa sia una questione di digestione e se c'è un modo per fare ciò che $ timeout sta facendo senza usare $ timeout.
Jameela Huq,

Puoi spiegare perché funziona? Sto provando ad accedere agli ID dinamici e funziona solo dopo un timeout con ritardo 0. Ho visto questa soluzione "hacky" in diversi post, ma nessuno spiega perché funzioni.
Jin

30

Ecco un approccio semplice ng-initche non richiede nemmeno una direttiva personalizzata. Ha funzionato bene per me in alcuni scenari, ad esempio la necessità di scorrere automaticamente un div di elementi ripetuti in modo continuo su un particolare elemento al caricamento della pagina, quindi la funzione di scorrimento deve attendere fino al ng-repeattermine del rendering sul DOM prima che possa essere attivato.

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

    };
});

Si noti che ciò è utile solo per le funzioni che è necessario chiamare una volta dopo il rendering iniziale di ng-repeat. Se devi chiamare una funzione ogni volta che il contenuto di ng-repeat viene aggiornato, dovrai usare una delle altre risposte su questo thread con una direttiva personalizzata.


1
Bello, questo ha fatto il lavoro. Volevo selezionare automaticamente il testo in una casella di testo e il timeout ha funzionato. Altrimenti, il testo {{model.value}} è stato selezionato e quindi deselezionato quando è stato iniettato il model.value associato a dati.
JoshGough,

27

A complemento della risposta di Pavel , qualcosa di più leggibile e facilmente comprensibile sarebbe:

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

Perché altro pensi che angular.noopci sia in primo luogo ...?

vantaggi:

Non devi scrivere una direttiva per questo ...


20
Perché usare un ternario quando puoi usare la logica booleana:ng-init="$last && doSomething( )"
Nate Whittaker,

È più facile leggere @NateWhittaker (ed è più difficile da leggere di un operatore ternario è impressionante). È bello avere un codice pulito e semplice da capire
Justin

21

Forse un approccio un po 'più semplice con ngInite Lodashdebounce metodo senza la necessità di direttiva personalizzata:

controller:

$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);

Modello:

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

Aggiornare

Esiste anche una soluzione AngularJS pura più semplice che utilizza l'operatore ternario:

Modello:

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

Tenere presente che ngInit utilizza la fase di compilazione pre-link, ovvero l'espressione viene invocata prima dell'elaborazione delle direttive figlio. Ciò significa che potrebbe essere ancora necessaria un'elaborazione asincrona.


Dovresti notare che hai bisogno di lodash.js per farlo funzionare :) Inoltre: la parte ", 20" di $ scope.refresh =, è quel numero di millisecondi prima che questo metodo venga chiamato dopo l'ultima itterazione?
Jørgen Skår Fischer,

Aggiunto Lodash davanti al link di rimbalzo . Sì, 20 è un ritardo prima dell'esecuzione del metodo - modificato in 0 in quanto non importa finché la chiamata è asincrona.
Pavel Horal,

1
Anche a questo proposito, dato che l'operatore ternario è autorizzato nelle espressioni semplici ng-init="$last ? refresh() : false"funzionerebbe pure. E questo non richiede nemmeno Lodash.
Pavel Horal,

<div ng-repeat = "i in [1,2,4]" ng-init = "doSomething ($ last)"> </div> $ scope.doSomething = function (lastElement) {if (lastElem) blah blah blah }
JSAddict,

4

Potrebbe anche essere necessario quando controlli la scope.$lastvariabile per avvolgere il trigger con a setTimeout(someFn, 0). A setTimeout 0è una tecnica accettata in javascript ed è stato indispensabile per il mio directivecorretto funzionamento.


ho riscontrato lo stesso problema. entrambi in backbone + angolare, se hai bisogno di fare qualcosa come leggere i valori delle proprietà css applicati a un elemento DOM, non sono riuscito a capire un modo senza l'hack timeout.
amorevole

3

Questo è un miglioramento delle idee espresse in altre risposte per mostrare come ottenere l'accesso alle proprietà ngRepeat ($ index, $ first, $ middle, $ last, $ even, $ odd) quando si utilizza la sintassi dichiarativa e l'ambito di isolamento ( Google ha raccomandato le migliori pratiche) con una direttiva elementare. Si noti la differenza 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!");
      }
    }
  };
});

Non sarebbe meglio usare le direttive come attributi che elementi? cioè limitare: 'A'?
Tejasvi Hegde,

2
Il punto era mostrare la sintassi dichiarativa + isolare l'ambito ... sì, se lo desideri, puoi usare qui una direttiva sugli attributi. C'è un tempo e un luogo per entrambi a seconda del tuo scopo.
JoshuaDavid,

3

L'ho fatto in questo modo.

Crea la direttiva

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

Dopo averlo aggiunto sull'etichetta in cui questo ng-repeat

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

E pronto con quello verrà eseguito alla fine della ng-repeat.


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

La mia soluzione era quella di aggiungere un div per chiamare una funzione se l'elemento era l'ultimo di una ripetizione.


2

vorrei aggiungere un'altra risposta, poiché le risposte precedenti indicano che il codice necessario per eseguire dopo che ngRepeat è stato eseguito è un codice angolare, che in tal caso tutte le risposte sopra forniscono una soluzione eccezionale e semplice, alcune più generiche di altre, e nel caso sia importante la fase del ciclo di vita del digest, puoi dare un'occhiata al blog di Ben Nadel a riguardo, ad eccezione dell'uso di $ parse invece di $ eval.

ma nella mia esperienza, come afferma l'OP, di solito esegue alcuni plug-in o metodi JQuery sul DOM compilato in modo definitivo, che in quel caso ho scoperto che la soluzione più semplice è creare una direttiva con un setTimeout, poiché la funzione setTimeout viene spinta alla fine della coda del browser, è sempre subito dopo aver fatto tutto in angolare, di solito ngReapet che continua dopo la funzione postLinking dei genitori

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

per chiunque si chieda che in quel caso perché non usare $ timeout, è che provoca un altro ciclo digest completamente inutile


1

Ho dovuto rendere le formule usando MathJax dopo che ng-repeat termina, nessuna delle risposte sopra ha risolto il mio problema, quindi ho fatto come di seguito. Non è una bella soluzione , ma ha funzionato per me ...

<div ng-repeat="formula in controller.formulas">
    <div>{{formula.string}}</div>
    {{$last ? controller.render_formulas() : ""}}
</div>

0

Ho trovato una risposta qui ben praticata, ma era ancora necessario aggiungere un ritardo

Creare la seguente direttiva:

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

Aggiungilo al tuo ripetitore come attributo, in questo modo:

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

Secondo Radu ,:

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

Per me funziona, ma devo ancora aggiungere un setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });

-4

Se vuoi semplicemente cambiare il nome della classe in modo che venga visualizzato in modo diverso, il codice sottostante farebbe il trucco.

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

Questo codice ha funzionato per me quando avevo un requisito simile per il rendering dell'articolo acquistato nella mia lista della spesa nel carattere trogolo Strick.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.