AngularJS: How to run additional code after AngularJS has rendered a template?

JavascriptJqueryDomAngularjs

Javascript Problem Overview


I have an Angular template in the DOM. When my controller gets new data from a service, it updates the model in the $scope, and re-renders the template. All good so far.

The issue is that I need to also do some extra work after the template has been re-rendered and is in the DOM (in this case a jQuery plugin).

It seems like there should be an event to listen to, such as AfterRender, but I can't find any such thing. Maybe a directive would be a way to go, but it seemed to fire too early as well.

Here is a jsFiddle outlining my problem: Fiddle-AngularIssue

== UPDATE ==

Based on helpful comments, I've accordingly switched to a directive to handle DOM manipulation, and implemented a model $watch inside the directive. However, I still am having the same base issue; the code inside of the $watch event fires before the template has been compiled and inserted into the DOM, therefore, the jquery plugin is always evaluating an empty table.

Interestingly, if I remove the async call the whole thing works fine, so that's a step in the right direction.

Here is my updated Fiddle to reflect these changes: http://jsfiddle.net/uNREn/12/

Javascript Solutions


Solution 1 - Javascript

First, the right place to mess with rendering are directives. My advice would be to wrap DOM manipulating jQuery plugins by directives like this one.

I had the same problem and came up with this snippet. It uses $watch and $evalAsync to ensure your code runs after directives like ng-repeat have been resolved and templates like {{ value }} got rendered.

app.directive('name', function() {
    return {
        link: function($scope, element, attrs) {
            // Trigger when number of children changes,
            // including by directives like ng-repeat
            var watch = $scope.$watch(function() {
                return element.children().length;
            }, function() {
                // Wait for templates to render
                $scope.$evalAsync(function() {
                    // Finally, directives are evaluated
                    // and templates are renderer here
                    var children = element.children();
                    console.log(children);
                });
            });
        },
    };
});

Hope this can help you prevent some struggle.

Solution 2 - Javascript

This post is old, but I change your code to:

scope.$watch("assignments", function (value) {//I change here
  var val = value || null;            
  if (val)
    element.dataTable({"bDestroy": true});
  });
}

see jsfiddle.

I hope it helps you

Solution 3 - Javascript

Following Misko's advice, if you want async operation, then instead of $timeout() (which doesn't work)

$timeout(function () { $scope.assignmentsLoaded(data); }, 1000);

use $evalAsync() (which does work)

$scope.$evalAsync(function() { $scope.assignmentsLoaded(data); } );

Fiddle. I also added a "remove row of data" link that will modify $scope.assignments, simulating a change to the data/model -- to show that changing the data works.

The Runtime section of the Conceptual Overview page explains that evalAsync should be used when you need something to occur outside the current stack frame, but before the browser renders. (Guessing here... "current stack frame" probably includes Angular DOM updates.) Use $timeout if you need something to occur after the browser renders.

However, as you already found out, I don't think there is any need for async operation here.

Solution 4 - Javascript

I have found the simplest (cheap and cheerful) solution is simply add an empty span with ng-show = "someFunctionThatAlwaysReturnsZeroOrNothing()" to the end of the last element rendered. This function will be run when to check if the span element should be displayed. Execute any other code in this function.

I realize this is not the most elegant way to do things, however, it works for me...

I had a similar situation, though slightly reversed where I needed to remove a loading indicator when an animation began, on mobile devices angular was initializing much faster than the animation to be displayed, and using an ng-cloak was insufficient as the loading indicator was removed well before any real data was displayed. In this case I just added the my return 0 function to the first rendered element, and in that function flipped the var that hides the loading indicator. (of course I added an ng-hide to the loading indicator triggered by this function.

Solution 5 - Javascript

I think you are looking for $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope#$evalAsync

Solution 6 - Javascript

Finally i found the solution, i was using a REST service to update my collection. In order to convert datatable jquery is the follow code:

$scope.$watchCollection( 'conferences', function( old, nuew ) {
        if( old === nuew ) return;
        $( '#dataTablex' ).dataTable().fnDestroy();
        $timeout(function () {
                $( '#dataTablex' ).dataTable();
        });
    });

Solution 7 - Javascript

i've had to do this quite often. i have a directive and need to do some jquery stuff after model stuff is fully loaded into the DOM. so i put my logic in the link: function of the directive and wrap the code in a setTimeout(function() { ..... }, 1); the setTimout will fire after the DOM is loaded and 1 milisecond is the shortest amount of time after DOM is loaded before code would execute. this seems to work for me but i do wish angular raised an event once a template was done loading so that directives used by that template could do jquery stuff and access DOM elements. hope this helps.

Solution 8 - Javascript

You can also create a directive that runs your code in the link function.

See that stackoverflow reply.

Solution 9 - Javascript

Neither $scope.$evalAsync() or $timeout(fn, 0) worked reliably for me.

I had to combine the two. I made a directive and also put a priority higher than the default value for good measure. Here's a directive for it (Note I use ngInject to inject dependencies):

app.directive('postrenderAction', postrenderAction);

/* @ngInject */
function postrenderAction($timeout) {
	// ### Directive Interface
	// Defines base properties for the directive.
	var directive = {
		restrict: 'A',
		priority: 101,
		link: link
	};
	return directive;

	// ### Link Function
	// Provides functionality for the directive during the DOM building/data binding stage.
	function link(scope, element, attrs) {
		$timeout(function() {
			scope.$evalAsync(attrs.postrenderAction);
		}, 0);
	}
}

To call the directive, you would do this:

<div postrender-action="functionToRun()"></div>

If you want to call it after an ng-repeat is done running, I added an empty span in my ng-repeat and ng-if="$last":

<li ng-repeat="item in list">
    <!-- Do stuff with list -->
    ...

    <!-- Fire function after the last element is rendered -->
    <span ng-if="$last" postrender-action="$ctrl.postRender()"></span>
</li>

Solution 10 - Javascript

In some scenarios where you update a service and redirect to a new view(page) and then your directive gets loaded before your services are updated then you can use $rootScope.$broadcast if your $watch or $timeout fails

View

<service-history log="log" data-ng-repeat="log in requiedData"></service-history>

Controller

app.controller("MyController",['$scope','$rootScope', function($scope, $rootScope) {

   $scope.$on('$viewContentLoaded', function () {
       SomeSerive.getHistory().then(function(data) {
           $scope.requiedData = data;
           $rootScope.$broadcast("history-updation");
       });
  });

}]);

Directive

app.directive("serviceHistory", function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
           log: '='
        },
        link: function($scope, element, attrs) {
            function updateHistory() {
               if(log) {
                   //do something
               }
            }
            $rootScope.$on("history-updation", updateHistory);
        }
   };
});

Solution 11 - Javascript

I came with a pretty simple solution. I'm not sure whether it is the correct way to do it but it works in a practical sense. Let's directly watch what we want to be rendered. For example in a directive that includes some ng-repeats, I would watch out for the length of text (you may have other things!) of paragraphs or the whole html. The directive will be like this:

.directive('myDirective', [function () {
    'use strict';
    return {
       
        link: function (scope, element, attrs) {
            scope.$watch(function(){
               var whole_p_length = 0;
               var ps = element.find('p');
                for (var i=0;i<ps.length;i++){
                    if (ps[i].innerHTML == undefined){
                        continue
                    }
                    whole_p_length+= ps[i].innerHTML.length;
                }
                //it could be this too:  whole_p_length = element[0].innerHTML.length; but my test showed that the above method is a bit faster
                console.log(whole_p_length);
                return whole_p_length;
            }, function (value) {   
                //Code you want to be run after rendering changes
            });
        }
}]);

NOTE that the code actually runs after rendering changes rather complete rendering. But I guess in most cases you can handle the situations whenever rendering changes happen. Also you could think of comparing this ps length (or any other measure) with your model if you want to run your code only once after rendering completed. I appreciate any thoughts/comments on this.

Solution 12 - Javascript

You can use the 'jQuery Passthrough' module of the angular-ui utils. I successfully binded a jQuery touch carousel plugin to some images that I retrieve async from a web service and render them with ng-repeat.

Solution 13 - Javascript

In my solution, I had a few custom directives that needed to be loaded first because they contained the definition of functions that their sibling directives call. For example:

<div id="container">
    <custom-directive1></custom-directive1>
    <custom-directive2></custom-directive2>
    <custom-directive3></custom-directive3>
</div>

Unfortunately, none of the solutions on here worked for me, because they only worked after rendering a directive, not the directive code behind.

So when I implemented any of the solutions above, to execute some load function, even though the directive were rendered, the scope didn't know what the functions inside those directives were.

So I created an observable anywhere in my controller:

//Call every time a directive is loaded
$scope.$watch('directiveLoaded', function (value) {
            debugger;
    if (value == document.querySelector('#container').children.length) {
        //Its ok to use childHead as we have only one child scope
        $scope.$$childHead.function1_Of_Directive1();
        $scope.$$childHead.function1_Of_Directive2();
    }
});

Then I have these two directives, where I place

scope.$parent.directiveLoaded += 1;

at the bottom of every directive. Because in the controller I have the observable defined, each time I update the variable directiveLoaded, it executes the observable function. Yes, I know this is a hack, but it's a small price to pay to guarantee all directives finish the rendering along with their code behind before executing the final function.

To complete the demo here are two directives that define the functions that need to be called later.

Directive1

(function () {
    app.directive('customDirective1', function () {
        return {
            restrict: 'E',
            templateUrl: '/directive1.html',
            link: function (scope) {

                scope.function1_Of_Directive1 = function() {
                    scope.function2_Of_Directive2();
                    console.log("F1_D1")
                }
     
                //AT BOTTOM OF EVERY DIRECTIVE
                scope.$parent.directiveLoaded += 1;
            }
        }
    });
})();

Directive2

(function () {
    app.directive('customDirective2', function () {
        return {
            restrict: 'E',
            templateUrl: '/directive1.html',
            link: function (scope) {
                
                scope.function1_Of_Directive2 = function() {
                    console.log("F1_D2")
                }
                scope.function2_Of_Directive2 = function() {
                    console.log("F2_D2")
                }

                //AT BOTTOM OF EVERY DIRECTIVE
                scope.$parent.directiveLoaded += 1;
            }
        }
    });
})();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongorebashView Question on Stackoverflow
Solution 1 - JavascriptdanijarView Answer on Stackoverflow
Solution 2 - JavascriptdipoldView Answer on Stackoverflow
Solution 3 - JavascriptMark RajcokView Answer on Stackoverflow
Solution 4 - Javascriptfuzion9View Answer on Stackoverflow
Solution 5 - JavascriptMisko HeveryView Answer on Stackoverflow
Solution 6 - JavascriptSergio CeronView Answer on Stackoverflow
Solution 7 - JavascriptmgrimmenatorView Answer on Stackoverflow
Solution 8 - JavascriptAnthony O.View Answer on Stackoverflow
Solution 9 - JavascriptXchaiView Answer on Stackoverflow
Solution 10 - JavascriptSudharshanView Answer on Stackoverflow
Solution 11 - JavascriptEhsan88View Answer on Stackoverflow
Solution 12 - JavascriptMichael Kork.View Answer on Stackoverflow
Solution 13 - JavascriptsksallajView Answer on Stackoverflow