(#2) Playing around custom directive in AngularJs

Directive is one of the most powerful feature of the angularjs but it is also one of the most confusing element of angularjs. In your template there would be so many repetitive components which would make our code bulky if we go forward by repeating the same code again and again so how about creating a custom directive which helps you repeat the component innumberable times with using ng-repeat irrespective of the position and template for the directive.

so how to use a directive, it could be used as an element, an attribute to another element, or even as a comment. Each directive could be restricted to the type of use using restrict key with value as ‘A’, ‘E’, ‘M’ or even ‘AEM’ or ‘AE’ or ‘EM’.

A: Attribute

E: Element

M: Comments

following code makes the attribute to be used as an attribute and as a element.

Creating a simplest custom directive

1
2
3
4
5
6
7
var app = angular.module('myApp', []);
app.directive('interestCard', function(){
return {
restrict: 'AE', // restrict the use of directive as element or attribute to an element
template: '<h1>viva la vida</h1>'
}
})

In the above directive we used template for defining how our directive would look like, we could add ‘replace: true,’ which will make sure that when our directive is called upon it actually replaces our directive with the template in directive thus you wont find the directive in final dom elements but you will find the template itself.

The template mentioned in above code is quite simple, it could be replaced by templareUrl to include a more complex template for the directive’s use.

Understanding scope in directive

Ever wondered what all options are available for angular to manipulate data at multiple levels in a single app. Manage data at main app controller, go with the modular approach in building app and handle separate isolated data model for making the isolated components work. In certain uses cases angular directive needs to interact with other controllers defined outside the directive to access certain functions and to change certain scope values. The directive could have its own controller or it can borrow it from main controller.

In the following section we will explore what all options we have in dealing with data and scoped objects at directive level. Scope in directive are of three types which includes: shared, inherited and isolated scopes. now what does it mean?

scope: false

Shared scope, when directive is using scope of controller. Normally scope in directive is always inherited by parent scope, thus making it accessable to functions and scope values defined in controller. Let me remind you any change in the directive in scope it will reflect in the controller’s scope.

1
2
3
4
5
6
7
app.directive('vivaLaVida', function() {
return {
restrict: 'A',
scope: false,
......
}
})

scope: true

Then there is a true condition where a new scope is created for the directive and change in directive scope will not reflect in the scope of controller this new scope is a disconnected child of the parent controller with all the same property but no tie with the parent.

scope: {}

when scope(directive) is completely isolated from scope of controller we go with ‘scope: {}’. well why isolated scope what’s the need to have a sperate scope when you already have one which can be accessed by all. the point to drive back home is you dont want your main controllers scope gets all messed up with logics which are specific to directives and would not be used anywhere else other then that directive.

1
2
3
4
5
6
app.directive('vivaLaVida', function(){
return{
restrict: 'AE',
scope: {}
}
})

scope elements could be made to connect with different elements through different binding levels, this makes isolated scope great since you have the enviornment totally isolated plus connectivity required as per need. this binding involves

@ : one way binding mostly used for passing strings values in scope
= : two way binding for sharing model. the model in parent scope is connected to isolated scope of directive.
& : binding is for passing methods into isolated scope of directive making it available to be called form inside of directive.

1
2
3
4
5
6
7
8
9
10
11
app.directive('vivaLaVida', function(){
return {
restrict: 'AE',
scope: {
messageText: '@',
userModel: '=',
alertFunction: '&'
},
.....
}
})

Compile your directive

compile function is like adding last end changes and functionality before the directive is pushed to DOm with its controller. you can have both compile and link function or inside link function you can have pre-link and post-link functions. its just the same.

now lets go deeper, lets bring some facts on the table regarding DOM and angular. If we go through what’s happening at the back then you would notice that at first whole of the DOM is loaded and then a broadcast is triggered when DOM is ready, angular listen to this trigger and parse the DOM searching for attribute ng-app when found it starts proccessing the DOM from that element.

Now its depends a lot on the directive’s defination for how angular would parse the directive. Following example would be helpful in understanding deeper concepts with compile and link functions.

following code reference from jvandemo.com

1
2
3
4
5
6
7
<level-one>  
<level-two>
<level-three>
Hello {{name}}
</level-three>
</level-two>
</level-one>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var app = angular.module('plunker', []);
function createDirective(name){
return function(){
return {
restrict: 'E',
compile: function(tElem, tAttrs){
console.log(name + ': compile');
return {
pre: function(scope, iElem, iAttrs){
console.log(name + ': pre link');
},
post: function(scope, iElem, iAttrs){
console.log(name + ': post link');
}
}
}
}
}
}
app.directive('levelOne', createDirective('levelOne'));
app.directive('levelTwo', createDirective('levelTwo'));
app.directive('levelThree', createDirective('levelThree'));

here is the output

console output

if you notice the output console you would see the order in which the code runs.

1
2
3
4
5
6
7
8
9
10
11
12
// COMPILE PHASE
// levelOne: compile function is called
// levelTwo: compile function is called
// levelThree: compile function is called
// PRE-LINK PHASE
// levelOne: pre link function is called
// levelTwo: pre link function is called
// levelThree: pre link function is called
// POST-LINK PHASE (Notice the reverse order)
// levelThree: post link function is called
// levelTwo: post link function is called
// levelOne: post link function is called

few points to notice, how angular run the compile of each element from top to bottom and then run the link function starting with pre-link in the same order but the post-link function is reverse order.

Compile function:

use compile function to change the original DOM elements before angualrjs creates instance of it and before scope is created. ng-repeat is a perfect example for this.

When compile runs scope is yet not assigned hence compile function can be defined as

1
2
3
4
5
6
7
8
9
/**
* Compile function
*
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(tElem, tAttrs){
// ...
};

pre-link function:

use pre-link function to manipulate the scope of the directive before the post-link function is called since we have the scope, elements and attributes but no controller. Controller only comes in the post-link function of the directive.

1
2
3
4
5
6
7
8
9
10
/**
* Pre-link function
*
* @param scope - scope assigned to the directive
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(scope, tElem, tAttrs){
// ...
};

post-link function:

run post-link function to execute the logic of your directive or instance since all child elements compile and pre-link functions have been executed.

1
2
3
4
5
6
7
8
9
10
/**
* Post-link function
*
* @param scope - scope assigned to the directive
* @param tElem - template element
* @param tAttrs - attributes of the template element
*/
function(scope, tElem, tAttrs){
// ...
};