Calling a function when ng-repeat has finished

前端 未结 10 1615
面向向阳花
面向向阳花 2020-11-22 02:28

What I am trying to implement is basically a \"on ng repeat finished rendering\" handler. I am able to detect when it is done but I can\'t figure out how to trigger a functi

相关标签:
10条回答
  • 2020-11-22 02:57

    The answers that have been given so far will only work the first time that the ng-repeat gets rendered, but if you have a dynamic ng-repeat, meaning that you are going to be adding/deleting/filtering items, and you need to be notified every time that the ng-repeat gets rendered, those solutions won't work for you.

    So, if you need to be notified EVERY TIME that the ng-repeat gets re-rendered and not just the first time, I've found a way to do that, it's quite 'hacky', but it will work fine if you know what you are doing. Use this $filter in your ng-repeat before you use any other $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;
        };
    })
    

    This will $emit an event called ngRepeatFinished every time that the ng-repeat gets rendered.

    How to use it:

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

    The ngRepeatFinish filter needs to be applied directly to an Array or an Object defined in your $scope, you can apply other filters after.

    How NOT to use it:

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

    Do not apply other filters first and then apply the ngRepeatFinish filter.

    When should I use this?

    If you want to apply certain css styles into the DOM after the list has finished rendering, because you need to have into account the new dimensions of the DOM elements that have been re-rendered by the ng-repeat. (BTW: those kind of operations should be done inside a directive)

    What NOT TO DO in the function that handles the ngRepeatFinished event:

    • Do not perform a $scope.$apply in that function or you will put Angular in an endless loop that Angular won't be able to detect.

    • Do not use it for making changes in the $scope properties, because those changes won't be reflected in your view until the next $digest loop, and since you can't perform an $scope.$apply they won't be of any use.

    "But filters are not meant to be used like that!!"

    No, they are not, this is a hack, if you don't like it don't use it. If you know a better way to accomplish the same thing please let me know it.

    Summarizing

    This is a hack, and using it in the wrong way is dangerous, use it only for applying styles after the ng-repeat has finished rendering and you shouldn't have any issues.

    0 讨论(0)
  • 2020-11-22 02:58

    Use $evalAsync if you want your callback (i.e., test()) to be executed after the DOM is constructed, but before the browser renders. This will prevent flicker -- ref.

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

    Fiddle.

    If you really want to call your callback after rendering, use $timeout:

    if (scope.$last) {
       $timeout(function() { 
          scope.$eval(attr.onFinishRender);
       });
    }
    

    I prefer $eval instead of an event. With an event, we need to know the name of the event and add code to our controller for that event. With $eval, there is less coupling between the controller and the directive.

    0 讨论(0)
  • 2020-11-22 02:58

    If you need to call different functions for different ng-repeats on the same controller you can try something like this:

    The 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');
                });
                }
            }
        }
    });
    

    In your controller, catch events with $on:

    $scope.$on('ngRepeatBroadcast1', function(ngRepeatFinishedEvent) {
    // Do something
    });
    
    $scope.$on('ngRepeatBroadcast2', function(ngRepeatFinishedEvent) {
    // Do something
    });
    

    In your template with multiple 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>
    
    0 讨论(0)
  • 2020-11-22 02:58

    I'm very surprised not to see the most simple solution among the answers to this question. What you want to do is add an ngInit directive on your repeated element (the element with the ngRepeat directive) checking for $last (a special variable set in scope by ngRepeat which indicates that the repeated element is the last in the list). If $last is true, we're rendering the last element and we can call the function we want.

    ng-init="$last && test()"
    

    The complete code for your HTML markup would be:

    <div ng-app="testApp" ng-controller="myC">
        <p ng-repeat="t in ta" ng-init="$last && test()">{{t}}</p>
    </div>
    

    You don't need any extra JS code in your app besides the scope function you want to call (in this case, test) since ngInit is provided by Angular.js. Just make sure to have your test function in the scope so that it can be accessed from the template:

    $scope.test = function test() {
        console.log("test executed");
    }
    
    0 讨论(0)
  • 2020-11-22 02:59

    A solution for this problem with a filtered ngRepeat could have been with Mutation events, but they are deprecated (without immediate replacement).

    Then I thought of another easy one:

    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');
                }
            }
        };
    });
    

    This hooks into the Node.prototype methods of the parent element with a one-tick $timeout to watch for successive modifications.

    It works mostly correct but I did get some cases where the altDone would be called twice.

    Again... add this directive to the parent of the ngRepeat.

    0 讨论(0)
  • 2020-11-22 03:00

    Very easy, this is how I did it.

    .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());
                }
            }
        };
    })

    0 讨论(0)
提交回复
热议问题