Ce que je tente d’implémenter est essentiellement un gestionnaire de rendu "le rendu terminé". Je suis capable de détecter quand cela est fait mais je n'arrive pas à comprendre comment en déclencher une fonction.
Vérifiez le violon: 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>
Answer: Le violon en train de finirmove: 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);
});
}
}
}
});
Notez que je n’ai pas utilisé .ready()
mais que je l’ai enveloppé dans un $timeout
. $timeout
s'assure qu'il est exécuté lorsque le rendu de tous les éléments ng-Répétés est VRAIMENT terminé (car le $timeout
sera exécuté à la fin du cycle de résumé en cours - et appellera également $apply
en interne, contrairement à setTimeout
). Ainsi, une fois que ng-repeat
est terminé, nous utilisons $emit
pour émettre un événement vers les étendues externes (étendues frère et parent).
Et puis dans votre contrôleur, vous pouvez l'attraper avec $on
:
$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
//you also get the actual event object
//do stuff, execute functions -- whatever...
});
Avec du HTML qui ressemble à quelque chose comme ça:
<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
<div>{{item.name}}}<div>
</div>
Utilisez $ evalAsync si vous voulez que votre callback (test ()) soit exécuté après la construction du DOM, mais avant le rendu du navigateur. Cela empêchera le scintillement - ref .
if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
Fiddle .
Si vous voulez vraiment appeler votre rappel après le rendu, utilisez $ timeout:
if (scope.$last) {
$timeout(function() {
scope.$eval(attr.onFinishRender);
});
}
Je préfère $ eval à la place d'un événement. Avec un événement, nous devons connaître le nom de l'événement et ajouter du code à notre contrôleur pour cet événement. Avec $ eval, il y a moins de couplage entre le contrôleur et la directive.
Les réponses données jusqu'à présent ne fonctionneront que la première fois que le ng-repeat
sera rendu , mais si vous avez un ng-repeat
dynamique, ce qui signifie que vous allez ajouter/supprimer/filtrer des éléments et que vous devez être averti chaque fois que le ng-repeat
est rendu, ces solutions ne fonctionneront pas pour vous.
Donc, si vous devez être averti CHAQUE FOIS que le ng-repeat
soit restitué et pas seulement la première fois, j'ai trouvé un moyen de le faire, c'est assez 'hacky', mais cela fonctionnera bien si Tu sais ce que tu fais. Utilisez ce $filter
dans votre ng-repeat
avant d’utiliser un autre $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;
};
})
Ceci $emit
un événement appelé ngRepeatFinished
chaque fois que le ng-repeat
sera rendu.
<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >
Le filtre ngRepeatFinish
doit être appliqué directement à une Array
ou à une Object
définie dans votre $scope
, vous pouvez appliquer d'autres filtres après.
<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >
N'appliquez pas d'abord d'autres filtres, puis appliquez le filtre ngRepeatFinish
.
Si vous souhaitez appliquer certains styles CSS dans le DOM après le rendu de la liste, vous devez tenir compte des nouvelles dimensions des éléments DOM qui ont été restitués par le ng-repeat
. (BTW: ce genre d'opérations devrait être fait dans une directive)
ngRepeatFinished
:N'effectuez pas de $scope.$apply
dans cette fonction, sinon vous placerez Angular dans une boucle infinie qu'il ne pourra pas détecter.
Ne l'utilisez pas pour apporter des modifications aux propriétés $scope
, car ces modifications ne seront reflétées dans votre vue que lors de la prochaine boucle $digest
et, étant donné que vous ne pouvez pas effectuer un $scope.$apply
, elles ne seront d'aucune utilité.
Non, ce n'est pas un hack, si vous n'aimez pas, ne l'utilisez pas. Si vous connaissez un meilleur moyen d'accomplir la même chose, faites-le-moi savoir.
Ceci est un hack , et l'utiliser de manière inappropriée est dangereux, utilisez-le uniquement pour appliquer des styles après le rendu du
ng-repeat
et vous ne devriez pas rencontrer de problèmes.
Si vous avez besoin d'appeler différentes fonctions pour différentes répétitions ng sur le même contrôleur, vous pouvez essayer quelque chose comme ceci:
La directive:
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');
});
}
}
}
});
Dans votre contrôleur, capturez les événements avec $ sur:
$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});
$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});
Dans votre modèle avec plusieurs 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>
Les autres solutions fonctionnent correctement lors du chargement initial de la page, mais l'appel de $ timeout à partir du contrôleur est le seul moyen de garantir que votre fonction est appelée lorsque le modèle est modifié. Voici un travail violon qui utilise $ timeout. Pour votre exemple ce serait:
.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
$timeout(function () {
test();
});
});
ngRepeat n'évalue une directive que lorsque le contenu de la ligne est nouveau. Par conséquent, si vous supprimez des éléments de votre liste, onFinishRender ne se déclenchera pas. Par exemple, essayez de saisir des valeurs de filtre dans ces fiddles emit .
Si vous n’êtes pas opposé à l’utilisation d’accessoires d’objet à deux dollars et que vous écrivez une directive dont le seul contenu est une répétition, il existe une solution assez simple (en supposant que vous ne vous préoccupiez que du rendu initial). Dans la fonction de lien:
const dereg = scope.$watch('$$childTail.$last', last => {
if (last) {
dereg();
// do yr stuff -- you may still need a $timeout here
}
});
C'est utile dans les cas où vous avez une directive qui doit manipuler DOM en fonction de la largeur ou de la hauteur des membres d'une liste rendue (ce qui est la raison la plus probable pour laquelle on poserait cette question), mais ce n'est pas aussi générique. comme les autres solutions proposées.
Très facile, c'est comme ça que je l'ai fait.
.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());
}
}
};
})
Une solution à ce problème avec ngRepeat filtré aurait pu être avec Mutation events, mais ils sont déconseillés (sans remplacement immédiat).
Puis j'ai pensé à un autre facile:
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');
}
}
};
});
Cela s'accroche aux méthodes Node.prototype de l'élément parent avec un délai d'expiration en une seule fois pour surveiller les modifications successives.
Cela fonctionne la plupart du temps correctement mais j’ai eu quelques cas où le altDone serait appelé deux fois.
Encore une fois ... ajoutez cette directive au parent de ngRepeat.
Je suis très surpris de ne pas trouver la solution la plus simple parmi les réponses à cette question . Ce que vous voulez faire est d’ajouter une directive ngInit
à votre élément répété (l’élément avec la directive ngRepeat
) en recherchant $last
variable définie dans la portée par ngRepeat
qui indique que l'élément répété est le dernier de la liste). Si $last
est vrai, nous convertissons le dernier élément et nous pouvons appeler la fonction souhaitée.
ng-init="$last && test()"
Le code complet de votre balise HTML serait:
<div ng-app="testApp" ng-controller="myC">
<p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
</div>
Vous n'avez pas besoin de code JS supplémentaire dans votre application en plus de la fonction d'étendue que vous souhaitez appeler (dans ce cas, test
) car ngInit
est fourni par Angular.js. Assurez-vous simplement d'avoir votre fonction test
dans la portée afin de pouvoir y accéder à partir du modèle:
$scope.test = function test() {
console.log("test executed");
}
Veuillez regarder le violon, http://jsfiddle.net/yNXS2/ . Comme la directive que vous avez créée n’a pas créé de nouvelle portée, j’ai poursuivi dans la voie.
$scope.test = function(){...
a rendu cela possible.