Llamar a una función cuando ng-repeat ha finalizado
Lo que estoy tratando de implementar es básicamente un controlador de "representación terminada y repetida". Puedo detectar cuándo está hecho, pero no puedo entender cómo activar una función a partir de él.
Mira el violín: 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>
Respuesta : Violín de trabajo desde el movimiento final: 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);
});
}
}
}
});
Observe que no lo usé .ready()
, sino que lo envolví en un archivo $timeout
. $timeout
se asegura de que se ejecute cuando los elementos repetidos ng REALMENTE hayan terminado de renderizarse (porque se $timeout
ejecutará al final del ciclo de resumen actual, y también llamará $apply
internamente, a diferencia desetTimeout
). Entonces, una vez ng-repeat
finalizado, utilizamos $emit
para emitir un evento a ámbitos externos (ámbitos hermano y padre).
Y luego en tu controlador, puedes capturarlo con $on
:
$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
//you also get the actual event object
//do stuff, execute functions -- whatever...
});
Con html que se parece a esto:
<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
<div>{{item.name}}}<div>
</div>
Utilice $evalAsync si desea que su devolución de llamada (es decir, test()) se ejecute después de que se construya el DOM, pero antes de que el navegador lo procese. Esto evitará el parpadeo - ref .
if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
Violín .
Si realmente desea llamar a su devolución de llamada después de renderizar, use $timeout:
if (scope.$last) {
$timeout(function() {
scope.$eval(attr.onFinishRender);
});
}
Prefiero $eval en lugar de un evento. Con un evento, necesitamos saber el nombre del evento y agregar código a nuestro controlador para ese evento. Con $eval, hay menos acoplamiento entre el controlador y la directiva.
Las respuestas que se han dado hasta ahora solo funcionarán la primera vez que se ng-repeat
renderice , pero si tiene una dinámica ng-repeat
, lo que significa que va a agregar/eliminar/filtrar elementos, y necesita que se le notifique cada vez que se ng-repeat
se procesa, esas soluciones no funcionarán para usted.
Entonces, si necesitas que te notifiquen CADA VEZ que se ng-repeat
vuelve a renderizar y no solo la primera vez, encontré una manera de hacerlo, es bastante "truco", pero funcionará bien si sabes lo que estás haciendo. haciendo. Utilice esto $filter
antes ng-repeat
de utilizar cualquier otro$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;
};
})
Este será $emit
un evento llamado ngRepeatFinished
cada vez que se ng-repeat
procese.
Cómo usarlo:
<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >
El ngRepeatFinish
filtro debe aplicarse directamente a un Array
o Object
definido en su $scope
, puede aplicar otros filtros después.
Cómo NO usarlo:
<li ng-repeat="item in (items | filter:{name:namedFiltered}) | ngRepeatFinish" >
No aplique otros filtros primero y luego aplique el ngRepeatFinish
filtro.
¿Cuándo debo usar esto?
Si desea aplicar ciertos estilos CSS en el DOM después de que la lista haya terminado de renderizarse, debe tener en cuenta las nuevas dimensiones de los elementos DOM que han sido renderizados nuevamente por el archivo ng-repeat
. (Por cierto: ese tipo de operaciones deben realizarse dentro de una directiva)
Qué NO HACER en la función que maneja el ngRepeatFinished
evento:
No realices una
$scope.$apply
función en esa función o pondrás a Angular en un bucle sin fin que Angular no podrá detectar.No lo use para realizar cambios en las
$scope
propiedades, porque esos cambios no se reflejarán en su vista hasta el siguiente$digest
ciclo y, como no puede realizarlos,$scope.$apply
no serán de ninguna utilidad.
"¡¡Pero los filtros no están hechos para usarse así!!"
No, no lo son, esto es un truco, si no te gusta no lo uses. Si conoce una mejor manera de lograr lo mismo, hágamelo saber.
resumiendo
Este es un truco y usarlo de manera incorrecta es peligroso. Úselo solo para aplicar estilos después de que haya
ng-repeat
terminado de renderizar y no debería tener ningún problema.
Si necesita llamar a diferentes funciones para diferentes ng-repeticiones en el mismo controlador, puede intentar algo como esto:
La directiva:
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');
});
}
}
}
});
En su controlador, capture eventos con $on:
$scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
// Do something
});
$scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
// Do something
});
En tu plantilla con múltiples 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>
Las otras soluciones funcionarán bien en la carga inicial de la página, pero llamar a $timeout desde el controlador es la única forma de garantizar que se llame a su función cuando cambie el modelo. Aquí hay un violín funcional que usa $timeout. Para tu ejemplo sería:
.controller('myC', function ($scope, $timeout) {
$scope.$watch("ta", function (newValue, oldValue) {
$timeout(function () {
test();
});
});
ngRepeat solo evaluará una directiva cuando el contenido de la fila sea nuevo, por lo que si elimina elementos de su lista, onFinishRender no se activará. Por ejemplo, intente ingresar valores de filtro en estos violines emit .