Wywołanie funkcji po zakończeniu ng-repeat

To, co próbuję zaimplementować, to w zasadzie "on ng repeat finished rendering". Jestem w stanie wykryć, kiedy to jest zrobione, ale nie mogę dowiedzieć się, jak uruchomić z niego funkcję.

Sprawdź skrzypce: http://jsfiddle.net/paulocoelho/BsMqq/3/

JS

var module = angular.module('testApp', [])
    .directive('onFinishRender', function () {
    return {
        restrict: '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

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" on-finish-render="test()">{{t}}</p>
</div>

Odpowiedź : Praca z finishingmove: http://jsfiddle.net/paulocoelho/BsMqq/4/

Author: PCoelho, 2013-03-04

9 answers

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

Zauważ, że nie użyłem .ready(), ale raczej owinąłem go w $timeout. $timeout upewnia się, że jest wykonywany, gdy powtórzone elementy NG naprawdę skończą renderowanie (ponieważ $timeout będzie wykonywany pod koniec bieżącego cyklu digest -- i będzie również wywoływał $apply wewnętrznie, w przeciwieństwie do setTimeout). Tak więc po zakończeniu ng-repeat, używamy $emit, aby emitować Zdarzenie do zewnętrznych zakresów (zakresów rodzeństwa i nadrzędnych).

I wtedy w kontrolerze możesz go złapać za $on:

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

Z html, który wygląda mniej więcej tak:

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>
 391
Author: holographic-principle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-27 12:16:35

Użyj $evalAsync Jeśli chcesz, aby wywołanie zwrotne (tj. test()) zostało wykonane po zbudowaniu DOM, ale przed renderowaniem przeglądarki. Zapobiega to migotaniu -- ref .

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

Fiddle .

Jeśli naprawdę chcesz wywołać callback po renderowaniu, użyj $timeout:

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

Wolę $eval zamiast eventu. W przypadku zdarzenia musimy znać jego nazwę i dodać kod do naszego kontrolera dla tego zdarzenia. Z $eval jest mniej sprzężenie pomiędzy sterownikiem a dyrektywą.

 87
Author: Mark Rajcok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-04 22:30:14

Odpowiedzi, które zostały udzielone do tej pory będą działać tylko przy pierwszym renderowaniu ng-repeat , ale jeśli masz dynamiczny ng-repeat, co oznacza, że będziesz dodawać/usuwać / filtrować elementy i musisz być powiadamiany za każdym razem, gdy ng-repeat zostanie wyrenderowany, te rozwiązania nie będą działać dla Ciebie.

Więc, jeśli musisz być powiadamiany za każdym razem, gdy ng-repeat zostanie ponownie renderowany i nie tylko za pierwszym razem, znalazłem na to sposób, to całkiem "hacky", ale będzie działać dobrze, jeśli wiesz, co robisz. Użyj tego $filter w swoim ng-repeat informacje ważne przed zastosowaniem innych $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;
    };
})

Spowoduje to $emit Zdarzenie o nazwie ngRepeatFinished za każdym razem, gdy ng-repeat zostanie wyrenderowany.

Jak go używać:

<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >

Filtr ngRepeatFinish musi być stosowany bezpośrednio do Array lub Object zdefiniowanych w $scope, możesz zastosować inne filtry po.

Jak go nie używać:

<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >

Nie stosować innych filtrów najpierw zastosuj filtr ngRepeatFinish.

Kiedy powinienem tego użyć?

Jeśli chcesz zastosować pewne style css do DOM po zakończeniu renderowania listy, ponieważ musisz wziąć pod uwagę nowe wymiary elementów DOM, które zostały ponownie renderowane przez ng-repeat. (BTW: tego typu operacje powinny być wykonywane wewnątrz dyrektywy)

Czego nie robić w funkcji obsługującej Zdarzenie ngRepeatFinished:

  • Nie wykonuj $scope.$apply w tym funkcja lub umieścisz Angular w nieskończonej pętli, której kątowy nie będzie w stanie wykryć.

  • Nie używaj go do wprowadzania zmian we właściwościach $scope, ponieważ zmiany te nie zostaną odzwierciedlone w widoku do następnej pętli $digest, a ponieważ nie możesz wykonać $scope.$apply, nie będą one przydatne.

"ale filtry nie powinny być używane w ten sposób!!"

Nie, Nie Są, to jest hack, jeśli ci się nie podoba, nie używaj go. If you know a better sposób, aby osiągnąć to samo proszę dać mi znać.

Podsumowanie

Jest to hack i używanie go w niewłaściwy sposób jest niebezpieczne, używaj go tylko do stosowania stylów po zakończeniu renderowania ng-repeat i nie powinieneś mieć żadnych problemów.

 27
Author: Josep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-29 03:14:23

Jeśli chcesz wywołać różne funkcje dla różnych powtórzeń ng na tym samym kontrolerze, możesz spróbować czegoś takiego:

Dyrektywa:

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

W kontrolerze możesz przechwytywać zdarzenia za pomocą $on:

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

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

W szablonie z wielokrotnością ng-repeat

<div ng-repeat="item in collection1" on-finish-render broadcasteventname="ngRepeatBroadcast1">
    <div>{{item.name}}}<div>
</div>

<div ng-repeat="item in collection2" on-finish-render broadcasteventname="ngRepeatBroadcast2">
    <div>{{item.name}}}<div>
</div>
 11
Author: MCurbelo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-05 16:43:05

Inne rozwiązania będą działać poprawnie przy początkowym ładowaniu strony, ale wywołanie $timeout z kontrolera jest jedynym sposobem, aby upewnić się, że funkcja zostanie wywołana, gdy Model się zmieni. Oto działający fiddle , który używa $timeout. Dla Twojego przykładu byłoby to:

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

NgRepeat oceni dyrektywę tylko wtedy, gdy zawartość wiersza jest nowa, więc jeśli usuniesz elementy z listy, onFinishRender nie uruchomi się. Na przykład spróbuj wprowadzić wartości filtrów w tych wierszach emitują .

 5
Author: John Harley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-31 01:44:06

Jeśli nie jesteś przeciwny używaniu dwuwymiarowych właściwości scope i piszesz dyrektywę, której jedyną zawartością jest powtórzenie, istnieje dość proste rozwiązanie (zakładając, że zależy Ci tylko na początkowym renderowaniu). W funkcji link:

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

Jest to przydatne w przypadkach, gdy masz dyrektywę, która musi wykonać manip dom na podstawie szerokości lub wysokości członków renderowanej listy (co myślę, że jest najbardziej prawdopodobnym powodem, dla którego można zadać to pytanie), ale nie jest tak ogólna jak inne zaproponowane rozwiązania.

 3
Author: Semicolon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 04:14:44

Rozwiązaniem tego problemu z filtrowanym ngRepeat mogło być Zdarzenie mutacja , ale są one przestarzałe(bez natychmiastowej wymiany).

Potem pomyślałem o innym łatwym:

app.directive('filtered',function($timeout) {
    return {
        restrict: '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');
            }
        }
    };
});

Ten hak do węzła.prototypowe metody elementu nadrzędnego z jednym kleszczem $timeout do obserwacji kolejnych modyfikacji.

Działa głównie poprawnie, ale dostałem kilka przypadków, w których altDone będzie wywoływany dwa razy.

Znowu... dodaj tę dyrektywę do rodzica ngRepeat.
 1
Author: Sjeiti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-22 08:57:13

Bardzo proste, tak to zrobiłem.

.directive('blockOnRender', function ($blockUI) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            
            if (scope.$first) {
                $blockUI.blockElement($(element).parent());
            }
            if (scope.$last) {
                $blockUI.unblockElement($(element).parent());
            }
        }
    };
})
 1
Author: CPhelefu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-28 16:26:20

Proszę spojrzeć na skrzypce, http://jsfiddle.net/yNXS2 / . ponieważ dyrektywa, którą stworzyłeś, nie stworzyła nowego zakresu, kontynuowałem tę drogę.

$scope.test = function(){... sprawił, że tak się stało.

 0
Author: Rajkamal Subramanian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-04 18:20:43