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/
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 setTimeout
auch 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>
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.
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.
<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.
<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.
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.)
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.
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.
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.
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>
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.
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.
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());
}
}
};
})
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.
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.