it-swarm.com.de

Aufruf einer Funktion, wenn ng-repeat beendet ist

Was ich versuche zu implementieren, ist im Grunde ein "on ng repeat beendet" -Handler. Ich kann erkennen, wann es fertig ist, aber ich kann nicht herausfinden, wie ich eine Funktion auslösen kann.

Überprüfen Sie die Geige: 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>

Beantworten Sie: Arbeitende Geige vom Finishingmove: http://jsfiddle.net/paulocoelho/BsMqq/4/

244
PCoelho
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);
                });
            }
        }
    }
});

Beachten Sie, dass ich .ready() nicht verwendet habe, sondern es in einen $timeout verpackt habe. $timeout stellt sicher, dass es ausgeführt wird, wenn die Wiederholung der ng-Elemente REALLY beendet ist (da $timeout am Ende des aktuellen Digest-Zyklus ausgeführt wird - und im Gegensatz zu setTimeoutauch intern $apply aufgerufen wird. Nachdem ng-repeat beendet ist, verwenden wir $emit, um ein Ereignis an äußere Bereiche (Geschwister- und übergeordnete Bereiche) auszugeben.

Und dann können Sie es in Ihrem Controller mit $on abfangen:

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

Bei html sieht das ungefähr so ​​aus:

<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
    <div>{{item.name}}}<div>
</div>
394

Verwenden Sie $ evalAsync , wenn Sie möchten, dass Ihr Rückruf (d. H. Test ()) ausgeführt wird, nachdem das DOM erstellt wurde, jedoch bevor der Browser rendert. Dies verhindert das Flimmern - ref .

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

Fiddle .

Wenn Sie Ihren Callback nach dem Rendern wirklich aufrufen möchten, verwenden Sie $ timeout:

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

Ich bevorzuge $ eval statt einer Veranstaltung. Bei einem Ereignis müssen wir den Namen des Ereignisses kennen und unserem Controller Code für dieses Ereignis hinzufügen. Mit $ eval besteht weniger Kopplung zwischen Controller und Direktive.

87
Mark Rajcok

Die Antworten, die bisher gegeben wurden, funktionieren nur beim ersten Mal, wenn der ng-repeat gerendert wird, aber wenn Sie einen dynamischen ng-repeat haben, bedeutet dies, dass Sie Elemente hinzufügen/löschen/filtern müssen, und Sie müssen dies tun jedes Mal, wenn der ng-repeat gerendert wird, benachrichtigt werden, funktionieren diese Lösungen nicht für Sie.

Also, wenn Sie JEDERZEIT benachrichtigt werden müssen, dass der ng-repeat erneut gerendert wird und nicht nur das erste Mal, ich habe einen Weg gefunden, das zu tun, es ist ziemlich "hacky", aber es wird gut funktionieren, wenn du weißt was du tust Verwenden Sie diesen $filter in Ihrem ng-repeat, bevor Sie einen anderen $filter verwenden:

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

Dies wird $emit ein Ereignis namens ngRepeatFinished jedes Mal, wenn der ng-repeat gerendert wird.

Wie man es benutzt:

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

Der ngRepeatFinish-Filter muss direkt auf eine Array oder eine Object angewendet werden, die in Ihrem $scope definiert ist. Sie können anschließend andere Filter anwenden.

Wie man es NICHT benutzt:

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

Wenden Sie zuerst keine anderen Filter an und wenden Sie dann den Filter ngRepeatFinish an.

Wann sollte ich das benutzen?

Wenn Sie nach dem Rendern der Liste bestimmte CSS-Stile in das DOM anwenden möchten, müssen Sie die neuen Dimensionen der DOM-Elemente berücksichtigen, die von ng-repeat erneut gerendert wurden. (Übrigens: Diese Art von Operationen sollte innerhalb einer Richtlinie durchgeführt werden.)

Was NICHT zu tun in der Funktion, die das Ereignis ngRepeatFinished behandelt:

  • Führen Sie in dieser Funktion keinen $scope.$apply aus. Andernfalls setzen Sie Angular in eine Endlosschleife, die Angular nicht erkennen kann.

  • Verwenden Sie es nicht, um Änderungen an den $scope-Eigenschaften vorzunehmen, da diese Änderungen erst in der nächsten $digest-Schleife in Ihrer Ansicht angezeigt werden, und da Sie keinen $scope.$apply ausführen können, sind sie nicht von Nutzen.

"Aber Filter sollten nicht so verwendet werden !!"

Nein, sie sind es nicht, das ist ein Hack, wenn Sie es nicht mögen, benutzen Sie es nicht. Wenn Sie einen besseren Weg kennen, um dasselbe zu erreichen, lassen Sie es mich wissen.

Zusammenfassend

Dies ist ein Hack, und die falsche Verwendung ist gefährlich. Verwenden Sie ihn nur zum Anwenden von Styles, nachdem der ng-repeat das Rendern beendet hat und Sie keine Probleme haben sollten.

27
Josep

Wenn Sie verschiedene Funktionen für verschiedene Wiederholungswiederholungen auf demselben Controller aufrufen müssen, können Sie Folgendes versuchen:

Die Richtlinie:

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

Fange Events in deinem Controller mit $ on:

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

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

In deiner Vorlage mit mehreren Wiederholungen

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

Die anderen Lösungen funktionieren beim ersten Laden der Seite einwandfrei. Das Aufrufen von $ timeout vom Controller aus ist jedoch die einzige Möglichkeit, sicherzustellen, dass Ihre Funktion aufgerufen wird, wenn sich das Modell ändert. Hier ist eine funktionierende Geige , die $ timeout verwendet. Für dein Beispiel wäre es:

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

ngRepeat wertet eine Direktive nur dann aus, wenn der Inhalt der Zeile neu ist. Wenn Sie Elemente aus Ihrer Liste entfernen, wird onFinishRender nicht ausgelöst. Versuchen Sie beispielsweise, Filterwerte in diesen Geigen emit einzugeben.

5
John Harley

Wenn Sie Doppel-Dollar-Requisiten nicht scheuen und eine Direktive schreiben, deren einziger Inhalt eine Wiederholung ist, gibt es eine ziemlich einfache Lösung (vorausgesetzt, Sie interessieren sich nur für das anfängliche Rendern). In der Linkfunktion:

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

Dies ist nützlich in Fällen, in denen Sie eine Direktive haben, die DOM-Manipulation basierend auf der Breite oder Höhe der Mitglieder einer gerenderten Liste vornehmen muss (was meiner Meinung nach der wahrscheinlichste Grund ist, warum Sie diese Frage stellen würden), aber es ist nicht so allgemein wie die anderen vorgeschlagenen Lösungen.

3
Semicolon

Ganz einfach, so habe ich es gemacht.

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

Eine Lösung für dieses Problem mit einem gefilterten ngRepeat hätte mit Mutation events sein können, diese werden jedoch nicht mehr empfohlen (ohne sofortigen Ersatz).

Dann dachte ich an einen anderen leichten:

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

Dies hakt sich in die Node.prototype-Methoden des übergeordneten Elements ein, mit einem Timeout von $ 1 für einen Tick, um auf aufeinanderfolgende Änderungen zu achten.

Es funktioniert größtenteils korrekt, aber ich habe einige Fälle bekommen, in denen der altDone zweimal aufgerufen würde.

Nochmals ... fügen Sie diese Direktive zum parent von ngRepeat hinzu.

1
Sjeiti

Ich bin sehr überrascht, nicht die einfachste Lösung unter den Antworten auf diese Frage zu sehen. Was möchten Sie tun? Fügen Sie eine ngInit-Direktive zu Ihrem wiederholten Element hinzu (das Element mit der ngRepeat-Direktive), das nach $last (einem speziellen Element) sucht Variable, die im Gültigkeitsbereich von ngRepeat festgelegt ist (was bedeutet, dass das wiederholte Element das letzte Element in der Liste ist). Wenn $last wahr ist, rendern wir das letzte Element und können die gewünschte Funktion aufrufen.

ng-init="$last && test()"

Der vollständige Code für Ihr HTML-Markup wäre:

<div ng-app="testApp" ng-controller="myC">
    <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>

Sie benötigen keinen zusätzlichen JS-Code in Ihrer App, außer der aufgerufenen Bereichsfunktion (in diesem Fall test), da ngInit von Angular.js bereitgestellt wird. Stellen Sie sicher, dass Ihre test-Funktion im Gültigkeitsbereich ist, damit Sie von der Vorlage darauf zugreifen können:

$scope.test = function test() {
    console.log("test executed");
}

Bitte schauen Sie sich die Geige an, http://jsfiddle.net/yNXS2/ . Da die von Ihnen erstellte Direktive keinen neuen Gültigkeitsbereich erstellt hat, habe ich diese Vorgehensweise fortgesetzt.

$scope.test = function(){... hat das möglich gemacht.