Llamar a una función cuando ng-repeat ha terminado

Lo que bash implementar es básicamente un controlador “on ng repeat finished rendering”. Puedo detectar cuándo está hecho pero no puedo descifrar 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

 

{{t}}

Respuesta : violín de trabajo de 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); }); } } } }); 

Tenga en cuenta que no .ready() sino que lo envolví en $timeout . $timeout se asegura de que se ejecute cuando los elementos ng-repeat hayan REALMENTE terminado el procesamiento (porque $timeout se ejecutará al final del ciclo de resumen actual, y también llamará $apply internamente, a diferencia de setTimeout ). Entonces, después de que la ng-repeat haya terminado, usamos $emit para emitir un evento a ámbitos externos (hermanos y ámbitos principales).

Y luego en su controlador, puede 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 así:

 
{{item.name}}}

Utilice $ evalAsync si desea que su callback (es decir, prueba ()) se ejecute después de que se construya el DOM, pero antes de que el navegador lo renderice. Esto evitará el parpadeo – ref .

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

Fiddle .

Si realmente desea llamar a su callback 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 ng-repeat , lo que significa que va a agregar / eliminar / filtrar elementos, y necesita se le notificará cada vez que se ng-repeat , esas soluciones no funcionarán para usted.

Entonces, si necesita que le notifiquen CADA VEZ que la ng-repeat se vuelve a renderizar y no solo la primera vez, he encontrado una manera de hacerlo, es bastante ‘hacky’, pero funcionará bien si usted 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 emitirá $emit un evento llamado ngRepeatFinished cada vez que se ngRepeatFinished ng-repeat .

Cómo usarlo:

 
  • El filtro ngRepeatFinish debe aplicarse directamente a una Array o un Object definido en $scope , luego puede aplicar otros filtros.

    Cómo NO usarlo:

     
  • No aplique otros filtros primero y luego aplique el filtro ngRepeatFinish .

    ¿Cuándo debería usar esto?

    Si desea aplicar ciertos estilos de CSS en el DOM después de que la lista haya finalizado la representación, porque debe tener en cuenta las nuevas dimensiones de los elementos de DOM que han sido reproducidos por ng-repeat . (Por cierto: ese tipo de operaciones debe hacerse dentro de una directiva)

    Qué NO HACER en la función que maneja el evento ngRepeatFinished :

    • No ejecute $scope.$apply en esa función o pondrá Angular en un bucle sin fin que Angular no podrá detectar.

    • No lo use para realizar cambios en las propiedades de $scope , porque esos cambios no se reflejarán en su vista hasta el próximo bucle $digest y, dado que no puede realizar un $scope.$apply , no serán de cualquier uso

    “¡Pero los filtros no están destinados a ser usados ​​así!”

    No, no lo son, este es un truco, si no te gusta no lo uses. Si conoce una forma mejor de lograr lo mismo, hágamelo saber.

    Resumiendo

    Este es un truco , y usarlo de la manera incorrecta es peligroso, utilízalo solo para aplicar estilos después de que la ng-repeat haya finalizado el procesamiento y no deberías tener ningún problema.

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

     
    {{item.name}}}
    {{item.name}}}

    Las otras soluciones funcionarán bien en la carga de la página inicial, pero llamar $ timeout desde el controlador es la única forma de garantizar que se invoque su función cuando cambie el modelo. Aquí hay un violín que usa $ timeout. Para su 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, no se iniciará en FinalizarRinder. Por ejemplo, intenta ingresar valores de filtro en estos violines emitidos .

    Si no te repugna utilizar accesorios de telescopio de dos dólares y estás escribiendo una directiva cuyo único contenido es una repetición, hay una solución bastante simple (suponiendo que solo te importe la representación 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 casos en los que tiene una directiva que necesita hacer DOM manip en función de los anchos o alturas de los miembros de una lista representada (que creo que es la razón más probable por la que se haría esta pregunta), pero no es tan genérica como las otras soluciones que se han propuesto.

    Una solución para este problema con un filtro ngRepeat podría haber sido con eventos de Mutación , pero están en desuso (sin reemplazo inmediato).

    Luego 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 enlaza los métodos Node.prototype del elemento padre con un tiempo de espera de un tick para ver las modificaciones sucesivas.

    Funciona en su mayoría correcto, pero tuve algunos casos en los que el altDone se llamaría dos veces.

    De nuevo … agregue esta directiva al padre de ngRepeat.

    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/ . Como la directiva que creó no creó un nuevo ámbito, continué en el camino.

    $scope.test = function(){... hecho que eso ocurra.