Saturday, January 24, 2015

Callback Function After Angular Finished Data Binding

Introduction:
This article mainly focuses on a solution of a common problem that usually being faced doing projects with angular and other JavaScript Frameworks. It's recommended that a basic knowledge of Angular data binding and directive is required for reading this article. A demo application is also created to help understand the solution more in detail.

Problem:
Often we need to bind different JavaScript events or initialize Jquery plugin after ng-bind-html or ng-repeat finished binding. As most of the time the data is retrieved from AJAX call so it's hard to estimate exact time when the DOM will be ready to bind those events or initialize plugins for new loaded element. One very common example would be, when we make a custom dynamic menu where data are bound using ng-repeat and jQuery is used to make all the animation. So the jQuery function, which contains the animation code, must call after data loaded successfully and angular completed binding all the DOM elements.

Solution:
We can solve this problem by creating a custom directive and send a callback function to that directive. For ng-repeat the directive will check whether scope's last object has completed binding and after that it will call the given callback function. Now for ng-html-bind it will watch scope’s htmlElement has any value and when it will see a value, it will call the given callback function. So here is a generic directive that will work for both ng-repeat and ng-html-bind:

app.directive('ngcDone', function ($timeout) {
    return function (scope, element, attrs) {
        scope.$watch(attrs.ngcDone, function (callback) {

            if (scope.$last === undefined) {
                scope.$watch('htmlElement', function () {
                    if (scope.htmlElement !== undefined) {
                        $timeout(eval(callback), 1);
                    }
                });
            }

            if (scope.$last) {
                eval(callback)();
            }
        });
    }
});

Let me give a short description of what is happening here. But before that it is important to know what is directive.

Directives are markers of DOM element which allow you to play with that specific DOM element.

Here we have created a custom directive name ngc-done and sending a callback function as string. The reason for sending it as string is just to make this directive a generic one. Like here if we want call a function which is define under scope then we can give it like ‘scope.YourFunction’.


We’ve used $watch inside the directive’s definition to track changes of our directive. $watch is a great feature of Angular and it takes two parameters. One is the object or property that we like to watch or track changes and another is a listener which will be called if any change is detected. Now we have used two $watch here. One is to track change of our directive and another is to track change of scope’s htmlElement if $last is undefined.

Now the $last is a property of scope which is related to ng-repeat and it’s a Boolean type and it will return true here if ng-repeat has finished binding the last value or object.  $last will return undefined if there is no ng-repeat in the DOM element. If this directive is used in the same element where ng-html-bind is used then $last will be undefined and then it will start watching scope’s htmlElement. If htmlElement is not undefined then directive will invoke the given callback function.

Using the Code:
It is recommended to download the source and there are three examples showing how to use this directive. 2 examples showing how to bind dynamic jQuery menu plugin, one with ng-repeat and another with a ng-html-bind and another example shows how to bind 2 lists and call callback function after it finished binding. The menu plugin that we've used here is available for purchase at CodeCanyon.

Referrences: