ng-powtórz zdarzenie końcowe

207

Chcę wywołać funkcję jQuery kierującą do div z tabelą. Ta tabela jest zapełniona ng-repeat.

Kiedy zadzwonię

$(document).ready()

Nie mam rezultatu

Również

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

nie pomaga

Czy jest jakiś sposób na wykonanie funkcji zaraz po zakończeniu zapełniania z powtórzeniem ng? Przeczytałem poradę dotyczącą korzystania z niestandardowego directive, ale nie mam pojęcia, jak go używać z ng-repeat i moim div ...

ChruS
źródło

Odpowiedzi:

241

Rzeczywiście, powinieneś używać dyrektyw i nie ma żadnego zdarzenia związanego z końcem pętli ng-Repeat (ponieważ każdy element jest konstruowany indywidualnie i ma swoje własne zdarzenie). Ale a) używanie dyrektyw może być wszystkim, czego potrzebujesz, i b) istnieje kilka właściwości specyficznych dla ng-Repeat, których możesz użyć, aby zrobić zdarzenie „po zakończeniu ngRepeat”.

W szczególności, jeśli chcesz tylko stylizować / dodawać zdarzenia do całej tabeli, możesz to zrobić za pomocą dyrektywy obejmującej wszystkie elementy ngRepeat. Z drugiej strony, jeśli chcesz adresować każdy element konkretnie, możesz użyć dyrektywy w ngRepeat, i będzie ona działać na każdy element po jego utworzeniu.

Następnie, nie są $index, $first, $middlei $lastwłaściwości można użyć do zdarzeń wyzwalających. W przypadku tego HTML:

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

Możesz użyć takich dyrektyw:

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

Zobacz to w akcji w tym Plunkerze . Mam nadzieję, że to pomoże!

Tiago Roldão
źródło
Czy mogę wykonać myMainDirectivekod dopiero po zakończeniu pętli? Muszę zaktualizować pasek przewijania div rodzica.
ChruS
2
Oczywiście! Wystarczy zmienić „window.alert” na funkcję wyzwalania zdarzeń i złapać ją za pomocą głównej dyrektywy. Jako przykład zaktualizowałem de Plunkera.
Tiago Roldão
9
Zaleca się najpierw dołączyć bibliotekę jQuery (przed Angular). Spowoduje to, że wszystkie elementy Angular zostaną opakowane w jQuery (zamiast jqLite). Następnie zamiast angular.element (element) .css () i $ (element) .children (). Css () możesz po prostu napisać element.css () i element.children (). Css (). Zobacz także groups.google.com/d/msg/angular/6A3Skwm59Z4/oJ0WhKGAFK0J
Mark Rajcok
11
Czy ktoś ma jakiś pomysł, jak to włączyć w przypadku kolejnych renderowań w dyrektywie ngRepeat? tj .: link
RavenHursT
1
Jest to konwencja nazewnictwa dla wszystkich dyrektyw kątowych. Zobacz docs.quarejs.org/guide/directive#normalization
Tiago Roldão
68

Jeśli chcesz po prostu wykonać kod na końcu pętli, oto nieco prostsza odmiana, która nie wymaga dodatkowej obsługi zdarzeń:

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

Zobacz demo na żywo.

Karlgold
źródło
Czy to działa, jeśli używam kontrolera jako składni? Proszę? Nie, czy jest jakiś inny sposób, który działa z kontrolerem jako?
Dan Nguyen
63

Nie ma potrzeby tworzenia dyrektywy, zwłaszcza po to, aby mieć ng-repeatpełne wydarzenie.

ng-init robi dla ciebie magię.

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

$lastpilnuje, że finishedtylko zostanie zwolniony, kiedy ostatni element został renderowane do DOM.

Nie zapomnij utworzyć $scope.finishedwydarzenia.

Happy Coding !!

EDYCJA: 23 października 2016 r

Jeśli chcesz również wywołać finishedfunkcję, gdy w tablicy nie ma żadnego elementu, możesz zastosować następujące obejście

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

Po prostu dodaj powyższą linię na górze ng-repeatelementu. Sprawdza, czy tablica nie ma żadnej wartości, i odpowiednio wywołuje funkcję.

Na przykład

<div ng-if="things.length > 0" ng-init="finished()"></div>
<div ng-repeat="thing in things" ng-init="$last && finished()">
Vikas Bansal
źródło
Co jeśli „rzeczy” okażą się pustą tablicą?
Frane Poljak,
1
@FranePoljak Dodałem rozwiązanie w odpowiedzi.
Vikas Bansal
1
W przypadku pustej tablicy
obsłuż
Napraw odpowiedź Vikas Bansal: // użyj pierwszego div, jeśli chcesz sprawdzić, czy tablica nie ma żadnej wartości, i odpowiednio wywołaj funkcję. <div ng-if = "! Things.length" ng-hide = "true" ng-init = "zakończeniu ()"> </div> <div ng-repeat = "rzecz w rzeczach" ng-init = "$ ostatnie i & zakończone () ">
Alex Teslenko
41

Oto powtórzona dyrektywa, która wywołuje określoną funkcję, gdy jest spełniona. Odkryłem, że wywoływana funkcja musi używać $ timeout z przedziałem = 0 przed wykonaniem manipulacji DOM, takich jak inicjowanie podpowiedzi na renderowanych elementach. jsFiddle: http://jsfiddle.net/tQw6w/

W $ scope.layoutDone spróbuj skomentować linię $ timeout i odkomentować „NOT CORRECT!” wiersz, aby zobaczyć różnicę w podpowiedziach.

<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
źródło
Nie chciałem używać limitu czasu $, ale kiedy powiedziałeś, że można go ustawić na 0, postanowiłem spróbować i zadziałało. Zastanawiam się, czy to kwestia trawienia i czy istnieje sposób na zrobienie tego, co robi limit czasu $ bez użycia limitu czasu $.
Jameela Huq,
Czy możesz wyjaśnić, dlaczego to działa? Próbuję uzyskać dostęp do dynamicznych identyfikatorów i działa to dopiero po przekroczeniu limitu czasu z zerowym opóźnieniem. Widziałem to „hacky” rozwiązanie w kilku postach, ale nikt nie wyjaśnia, dlaczego to działa.
Jin
30

Oto proste podejście, ng-initktóre nie wymaga nawet niestandardowej dyrektywy. Działa to dla mnie dobrze w niektórych scenariuszach, np. Konieczności automatycznego przewijania div powtórzeń ng do konkretnego elementu podczas ładowania strony, więc funkcja przewijania musi poczekać, aż ng-repeatzakończy renderowanie w DOM, zanim będzie mógł uruchomić.

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

    };
});

Zauważ, że jest to przydatne tylko w przypadku funkcji, które musisz wywołać jeden raz po początkowym renderowaniu ng-repeat. Jeśli musisz wywołać funkcję przy każdej aktualizacji zawartości powtórzeń ng, będziesz musiał użyć jednej z pozostałych odpowiedzi w tym wątku z niestandardową dyrektywą.

dshap
źródło
1
Piękne, to zadziałało. Chciałem automatycznie zaznaczyć tekst w polu tekstowym, a limit czasu zadziałał. W przeciwnym razie tekst {{model.value}} został zaznaczony, a następnie odznaczony po wstrzyknięciu modelu.value powiązanego z danymi.
JoshGough
27

Uzupełnieniem odpowiedzi Pavela jest coś bardziej czytelnego i zrozumiałego:

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

Jak myślisz, dlaczego jeszcze angular.nooptam jest ...?

Zalety:

Nie musisz pisać dyrektywy dla tego ...

deostroll
źródło
20
Po co używać trójskładnika, gdy można użyć logiki logicznej:ng-init="$last && doSomething( )"
Nate Whittaker
Jest łatwiejszy do odczytania @NateWhittaker (i trudniej go odczytać niż operator trójskładowy robi wrażenie). Dobrze jest mieć czysty i prosty do zrozumienia kod
Justin
21

Może nieco prostsze podejście ngIniti debouncemetoda Lodasha bez potrzeby niestandardowej dyrektywy:

Kontroler:

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

Szablon:

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

Aktualizacja

Istnieje jeszcze prostsze rozwiązanie AngularJS wykorzystujące operator trójskładnikowy:

Szablon:

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

Należy pamiętać, że ngInit wykorzystuje fazę kompilacji przed połączeniem - tzn. Wyrażenie jest wywoływane przed przetworzeniem dyrektyw potomnych. Oznacza to, że nadal może być wymagane przetwarzanie asynchroniczne.

Pavel Horal
źródło
Należy zauważyć, że potrzebujesz do tego lodash.js :) Ponadto: część „, 20” $ scope.refresh =, czy ta liczba milisów przed wywołaniem tej metody po ostatniej iteracji?
Jørgen Skår Fischer
Dodano Lodash przed czasową link. Tak, 20 jest opóźnieniem przed wykonaniem metody - zmieniono na 0, ponieważ nie ma znaczenia, dopóki połączenie jest asynchroniczne.
Pavel Horal
1
Pomyślenie o tym, ponieważ operator wyrażeń trójskładnikowych jest dozwolony w wyrażeniach, ng-init="$last ? refresh() : false"również działałoby. I to nawet nie wymaga 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 }
JSAddict
4

Może być również konieczne, gdy zaznaczysz scope.$lastzmienną, aby owinąć wyzwalacz znakiem setTimeout(someFn, 0). A setTimeout 0jest przyjętą techniką w javascript i konieczne było, directiveaby działał poprawnie.

Cody
źródło
znalazłem ten sam problem. zarówno w sieci szkieletowej, jak i kątowej, jeśli potrzebujesz zrobić coś takiego, jak odczytanie wartości właściwości css zastosowanych do elementu DOM, nie mogłem znaleźć sposobu bez hakowania limitu czasu.
chovy,
3

Jest to ulepszenie pomysłów wyrażonych w innych odpowiedziach w celu pokazania, jak uzyskać dostęp do właściwości ngRepeat ($ index, $ first, $ middle, $ last, $ even, $ nieparzyste) przy użyciu deklaratywnej składni i izolowania zakresu ( Google zaleca najlepszą praktykę) wraz z dyrektywą dotyczącą elementów. Zauważ różnicę podstawowy: 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
źródło
Czy nie byłoby lepiej używać dyrektyw jako atrybutów niż elementów? tj. ograniczyć: „A”?
Tejasvi Hegde,
2
Chodziło o to, aby pokazać deklaratywną składnię + wyodrębnić zakres ... tak, możesz tutaj użyć dyrektywy atrybutów, jeśli chcesz. Jest czas i miejsce, w zależności od celu.
JoshuaDavid
3

Zrobiłem to w ten sposób.

Utwórz dyrektywę

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

Po dodaniu go na etykiecie, gdzie to powtórzenie ng

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

I gotowe z tym będą uruchamiane na końcu powtórzenia ng.

Mvochoa
źródło
3
<div ng-repeat="i in items">
        <label>{{i.Name}}</label>            
        <div ng-if="$last" ng-init="ngRepeatFinished()"></div>            
</div>

Moim rozwiązaniem było dodanie div, aby wywołać funkcję, jeśli element był ostatnim w powtórzeniu.

scotty3
źródło
2

chciałbym dodać inną odpowiedź, ponieważ poprzednie odpowiedzi zakładają, że kod potrzebny do uruchomienia po zakończeniu ngRepeat jest kodem kątowym, który w tym przypadku wszystkie powyższe odpowiedzi dają świetne i proste rozwiązanie, niektóre bardziej ogólne niż inne, a jeśli jest to ważne etap cyklu życia, możesz spojrzeć na blogu Bena Nadela na ten temat, z wyjątkiem użycia $ parse zamiast $ eval.

ale z mojego doświadczenia wynika, że ​​- jak twierdzi OP - zwykle uruchamia niektóre wtyczki JQuery lub metody w skompilowanym domenie DOM, co w takim przypadku uznałem, że najprostszym rozwiązaniem jest stworzenie dyrektywy z setTimeout, ponieważ funkcja setTimeout jest wypychana do końca kolejki przeglądarki jest ona zawsze tuż po tym, jak wszystko jest zrobione pod kątem, zwykle ngReapet, który jest kontynuowany po funkcji nadrzędnej postLinking

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

dla każdego, kto zastanawia się, czy w takim przypadku nie użyć $ timeout, powoduje to, że kolejny cykl trawienia jest całkowicie niepotrzebny

bresleveloper
źródło
1

Musiałem renderować formuły za pomocą MathJax po zakończeniu powtórzeń ng, żadna z powyższych odpowiedzi nie rozwiązała mojego problemu, więc zrobiłem to jak poniżej. To nie jest fajne rozwiązanie , ale działało dla mnie ...

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

Znalazłem tutaj odpowiedź dobrze przećwiczoną, ale nadal trzeba było dodać opóźnienie

Utwórz następującą dyrektywę:

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

Dodaj go do repeatera jako atrybut, jak poniżej:

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

Według Radu:

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

Dla mnie to działa, ale wciąż muszę dodać setTimeout

$scope.eventoSelecionado.internamento_evolucoes.forEach(ie => {
setTimeout(function() { 
    mycode
}, 100); });
Blomersi
źródło
-4

Jeśli po prostu chcesz zmienić nazwę klasy, aby była renderowana inaczej, poniższy kod załatwi sprawę.

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

Ten kod działał dla mnie, gdy miałem podobny wymóg renderowania zakupionego przedmiotu na mojej liście zakupów czcionką koryta Strick.

użytkownik4808836
źródło