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:
- AngularJs API Docs: https://docs.angularjs.org/api
- John Papa: http://www.johnpapa.net/
- Dan Wahlen: https://weblogs.asp.net/dwahlin
- Ben Nadel: http://www.bennadel.com/