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

Resuelto PCoelho asked hace 11 años • 10 respuestas

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/

PCoelho avatar Mar 05 '13 00:03 PCoelho
Aceptado
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. $timeoutse asegura de que se ejecute cuando los elementos repetidos ng REALMENTE hayan terminado de renderizarse (porque se $timeoutejecutará al final del ciclo de resumen actual, y también llamará $applyinternamente, a diferencia desetTimeout ). Entonces, una vez ng-repeatfinalizado, utilizamos $emitpara 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>
holographic-principle avatar Mar 04 '2013 18:03 holographic-principle

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.

Mark Rajcok avatar Mar 04 '2013 19:03 Mark Rajcok

Las respuestas que se han dado hasta ahora solo funcionarán la primera vez que se ng-repeatrenderice , 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-repeatse procesa, esas soluciones no funcionarán para usted.

Entonces, si necesitas que te notifiquen CADA VEZ que se ng-repeatvuelve 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 $filterantes 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á $emitun evento llamado ngRepeatFinishedcada vez que se ng-repeatprocese.

Cómo usarlo:

<li ng-repeat="item in (items|ngRepeatFinish) | filter:{name:namedFiltered}" >

El ngRepeatFinishfiltro debe aplicarse directamente a un Arrayo Objectdefinido 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 ngRepeatFinishfiltro.

¿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 ngRepeatFinishedevento:

  • No realices una $scope.$applyfunció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 $scopepropiedades, porque esos cambios no se reflejarán en su vista hasta el siguiente $digestciclo y, como no puede realizarlos, $scope.$applyno 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-repeatterminado de renderizar y no debería tener ningún problema.

Josep avatar Sep 29 '2014 03:09 Josep

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>
MCurbelo avatar Jun 05 '2015 15:06 MCurbelo

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 .

John Harley avatar Jan 31 '2014 01:01 John Harley