events - example - ng repeat key value
Llamando a una función cuando ng-repeat ha finalizado (9)
Las otras soluciones funcionarán bien en la carga de la página inicial, pero llamar a $ timeout desde el controlador es la única manera de garantizar que se llame a su función cuando cambie el modelo. Aquí hay un fiddle trabajo 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 problemas emitidos.
Lo que estoy tratando de implementar es, básicamente, un controlador de "en la repetición ng finalizada de procesamiento". Soy capaz de detectar cuándo está hecho, pero no puedo averiguar cómo activar una función desde ahí.
Compruebe 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 : Trabajando con el violín desde el final: http://jsfiddle.net/paulocoelho/BsMqq/4/ : http://jsfiddle.net/paulocoelho/BsMqq/4/
Muy fácil, así es como lo hice.
.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());
}
}
};
})
Por favor, eche un vistazo al violín, http://jsfiddle.net/yNXS2/ . Dado que la directiva que creó no creó un nuevo alcance, continué en el camino.
$scope.test = function(){...
hizo que eso sucediera.
Si necesita llamar a diferentes funciones para diferentes repeticiones ng 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 tu controlador, captura eventos con $ en:
$scope.$on(''ngRepeatBroadcast1'', function(ngRepeatFinishedEvent) {
// Do something
});
$scope.$on(''ngRepeatBroadcast2'', function(ngRepeatFinishedEvent) {
// Do something
});
En su plantilla con repetición múltiple ng
<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>
Si no es reacio a usar accesorios de alcance de doble dólar y está escribiendo una directiva cuyo único contenido es una repetición, hay una solución bastante simple (suponiendo que solo le importa el renderizado inicial). En la función de enlace:
const dereg = scope.$watch(''$$childTail.$last'', last => {
if (last) {
dereg();
// do yr stuff -- you may still need a $timeout here
}
});
Esto es útil para los casos en los que tiene una directiva que necesita hacer manipulación de DOM según los anchos o las alturas de los miembros de una lista representada (lo cual creo que es la razón más probable por la que uno haría esta pregunta), pero no es tan genérico. Como las otras soluciones que se han propuesto.
Una solución para este problema con un ngRepeat filtrado podría haber sido con eventos de Mutation , pero están en desuso (sin reemplazo inmediato).
Entonces pensé en otra fácil:
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'');
}
}
};
});
Esto se enlaza con los métodos de prototipo Nodo del elemento padre con un tiempo de espera de un solo tilde para observar modificaciones sucesivas.
Funciona en su mayoría correcto, pero tuve algunos casos en los que se llamaba al altDone dos veces.
Nuevamente ... agregue esta directiva al padre de ngRepeat.
Use $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 se muestre el navegador. Esto evitará el parpadeo - ref .
if (scope.$last) {
scope.$evalAsync(attr.onFinishRender);
}
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
, pero si tiene una ng-repeat
dinámica, lo que significa que va a agregar / eliminar / filtrar elementos, y necesita se le notificará cada vez que se procesa la ng-repeat
, esas soluciones no funcionarán para usted.
Por lo tanto, si necesita que le notifiquen CADA VEZ que la ng-repeat
se vuelve a representar y no solo la primera vez, he encontrado una forma de hacerlo, es bastante "intrépido", pero funcionará bien si lo sabe. qué estás haciendo. Use este $filter
en su ng-repeat
antes de usar 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;
};
})
Esto $emit
un evento llamado ngRepeatFinished
cada vez que se ng-repeat
.
Cómo usarlo:
<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >
El filtro ngRepeatFinish
debe aplicarse directamente a una Array
o un 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 filtro ngRepeatFinish
.
¿Cuándo debo usar esto?
Si desea aplicar ciertos estilos css en el DOM después de que la lista haya terminado de procesarse, ya que debe tener en cuenta las nuevas dimensiones de los elementos DOM que se han vuelto a representar con la ng-repeat
. (Por cierto: ese tipo de operaciones deben realizarse dentro de una directiva)
Qué NO HACER en la función que maneja el evento ngRepeatFinished
:
No realice un
$scope.$apply
en esa función o colocará Angular en un bucle sin fin que Angular no podrá detectar.No lo use para realizar cambios en las propiedades de
$scope
, ya que dichos cambios no se reflejarán en su vista hasta el próximo bucle de$digest
, y como no puede realizar un$scope.$apply
Cualquier uso.
"¡Pero los filtros no están destinados a ser usados de esa manera!"
No, no lo son, esto es un truco, si no te gusta, no lo uses. Si conoce una mejor manera de lograr lo mismo, por favor hágamelo saber.
Resumiendo
Esto es un pirateo , y usarlo de manera incorrecta es peligroso, utilícelo solo para aplicar estilos después de que
ng-repeat
haya terminado de renderizarse y no debería tener ningún problema.
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 .ready()
sino que lo envolví en un $timeout
. $timeout
se asegura de que se ejecute cuando los elementos repetidos ng hayan finalizado REALMENTE la representación (porque $timeout
se ejecutará al final del ciclo de compendio actual, y también llamará a $apply
internamente, a diferencia de setTimeout
). Por lo tanto, una vez finalizada la ng-repeat
, usamos $emit
para emitir un evento a ámbitos externos (ámbitos hermanos y padres).
Y luego en tu controlador, puedes atraparlo con $on
:
$scope.$on(''ngRepeatFinished'', function(ngRepeatFinishedEvent) {
//you also get the actual event object
//do stuff, execute functions -- whatever...
});
Con html que se ve algo como esto:
<div ng-repeat="item in items" on-finish-render="ngRepeatFinished">
<div>{{item.name}}}<div>
</div>