2

There are tons of posts showing how to have callback functions based on a directive so that when an ng-repeat function is finished you can wait and then call a function. For example here is my example.

<div ng-repeat="Object in Objects" class="objectClass" on-finish-render>{{Object.Overlay}</div>

Then when it is completed the following calls my function

.directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    scope.$emit('ngRepeatFinished');
                }, 0);
            }
        }
    }
});

This successfully calls my function below when it is completed

$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
    //my code goes here
}

Now all of that works perfectly and as intended the first time I set my $scope.Objects variable. My code will wait until all objects are fully rendered and then runs, literally perfect. However if after the initial everything I change $scope.Objects again, my function will still run but it will NOT wait for the completion. Its actually visible with console logs, the first go round it will pause for about half a second after I go into the directive but before the actual emit, but on subsequent changes to my ng-repeat it does not pause and simply calls my function before the dom is finished rendering.

This is super annoying and any help would be great!

CMOS
  • 2,727
  • 8
  • 47
  • 83
  • Hi @Shohel Just wondering, in this senario, what is the difference between `promise` and `$emit`? – Tyler.z.yang Feb 02 '16 at 02:27
  • Hi @Calvin Moss , you should notice every time you change something in $scope.Objects it will trigger ng-repeat to rerender. And then you directive `onFinishRender ` emit an event. Do you want to emit the event only in the first time or? – Tyler.z.yang Feb 02 '16 at 02:29
  • Right, that is working, what is not working is the timeout. My function is running AFTER the completed render on the first run, and BEFORE on subsequent runs. I want the function to run EVERY SINGLE TIME I edit the $scope.Objects, and I want it to wait to fully render and run after, every single time. – CMOS Feb 02 '16 at 02:31
  • How do you know it calls your function before the dom is finished rendering? Can you post some runable code to jsfiddle or somewhere else so that I can debug? – Tyler.z.yang Feb 02 '16 at 02:36
  • I can try and make a jsfiddle, I know it is happening because I am getting errors in my function after the first run. However if I set a very small delay on the $timeout function, say 100 ms, it works fine. I know this is because of the delay, I will work on a jsfiddle. – CMOS Feb 02 '16 at 02:38
  • To understand why your event is being emitted (or not being emitted), you need to understand more about `ng-repeat`...see my answer below. – Shaun Scovil Feb 02 '16 at 03:02

2 Answers2

2

Angular’s $emit, $broadcast and $on fall under the common “publish/subscribe” design pattern, or can do, in which you’d publish an event and subscribe/unsubscribe to it somewhere else. The Angular event system is brilliant, it makes things flawless and easy to do (as you’d expect!) but the concept behind it isn’t so simple to master and you can often be left wondering why things don’t work as you thought they might.

Using $scope.$emit will fire an event up the $scope. Using $scope.$broadcast will fire an event down the $scope. Using $scope.$on is how we listen for these events.

(reference)

I have provided two solution according to your problem.

Solution One

<div ng-controller="MyCtrl">
    <div ng-repeat="Object in Objects" class="objectClass" on-finish-render isolated-expression-foo="updateItem(item,temp)">{{Object|json}</div>
</div>

var app = angular.module('app', []);    
app.directive('onFinishRender', function () {
    return {
        restrict: 'A',
        scope: {
            isolatedExpressionFoo: '&'
        },
        link: function (scope, element, attr) {
            if (scope.$parent.$last) {
                scope.isolatedExpressionFoo({ temp: "some value" });
            }
        }
    };
});

app.controller('MyCtrl', ['$scope', function ($scope) {

    $scope.Objects = [{ id: 1, value: "test" }, { id: 2, value: "TEst2" }];

    $scope.updateItem = function (item, temp) {
        console.log("Item param " + item.id);
        console.log("temp param " + temp);
    }
}]);

Solution Two

<div ng-controller="MyCtrl">
     <div ng-repeat="Object in Objects" class="objectClass" on-finish-render>{{Object|json}</div>
</div>

var app = angular.module('app', []);    
app.directive('onFinishRender', function ($rootScope) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last) {
                $rootScope.$broadcast("ngRepeatFinished", { temp: "some value" });
            }
        }
    };
});

app.controller('MyCtrl', ['$scope', function ($scope) {

    $scope.Objects = [{ id: 1, value: "test" }, { id: 2, value: "TEst2" }];

    $scope.$on('ngRepeatFinished', function (temp) {
        console.log("Item param " + temp);
    });

}]);
Community
  • 1
  • 1
Shohel
  • 3,886
  • 4
  • 38
  • 75
2

One thing you should understand about ng-repeat is that it reuses DOM elements whenever possible, so if you have two objects in your repeater and you add a third, the first two elements will not be re-rendered. Only the third will be rendered, thus your directive's link function will only be called once for the newly-added object.

Similarly, if you remove an item, your directive's link function will not be run again.

Observe the behavior in this JSFiddle.

Shaun Scovil
  • 3,905
  • 5
  • 39
  • 58