Directive Definition
The simplest way to think about a directive is that it is simply a function that we run on a particular DOM element.
We could use .directive() method to create it:
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function($timeout, UserDfinedService){
//
});
The directive() method takes two arguments:
name(string): The name of the directive as a string that we'll refer to inside of our views.
factory_function(function): The factory function returns an object that defines how the directive behaves. It is expected to return an object providing options that tell the $compile service how the directive should behave when it is invoked in the DOM.
When Angular bootstraps our app, it will register the returned object by the name provided as a string via the first argument. The Angular compiler parses the DOM of our main HTML document looking for elements, attributes, comments, or class names using that name when looking for these directives. When it finds one that it knows about, it uses the directive definition to place the DOM element on the page.
The factory function we define for a directive is only invoked once, when the compiler matches the directive the first time. Just like the .controller function, we invoke a directive's factory function using $injector.invoke.
angular.module('myApp', []) .directive('myDirective', function() {
return {
restrict: String,
priority: Number,
terminal: Boolean,
template: String or Template Function:
function(tElement, tAttrs) (...},
templateUrl: String,
replace: Boolean or String,
scope: Boolean or Object, transclude: Boolean,
controller: String or
function(scope, element, attrs, transclude, otherInjectables) { ....},
controllerAs: String,
require: String,
link: function(scope, iElement, iAttrs) { ... },
compile: return an Object OR
function(tElement, tAttrs, transclude) {
return {
pre: function(scope, iElement, iAttrs, controller) { ... },
post: function(scope, iElement, iAttrs, controller) { ... }
}
// or
return function postLink(...) { ... } }
}; });
Restrict(string)
restrict is an optional argument. It is responsible for telling Angular in which format our directive will be declared in the DOM.
The available options are as follows:
1. E(an element)
2. A(an attribute, default)
3. C(a class)
4. M(a comment)
These options can be used alone or in combination.
Priority(number)
The priority option can be set to a number.
Terminal(boolean)
We use the terminal option to tell Angular to stop invoking any further directives on an element that have a higher priority.All directives with the same priority will be executed, however.
Template(string|function)
template is optional. If provided, it must be set to either:
1. a string of HTML
2. a function that takes two arguments - tElement and tAttrs - and returns a string value representing the template. The t in tElement and tAttrs stands for template, as opposed to instance.
When a template string contains more than one DOM element or only a single text node, it must be wrapped in a parent element.In other words, a root DOM element must exist:
template: '\
<div> <-- single root element -->\
<a href="http://google.com">Click me</a>\
<h1>When using two elements, wrap them in a parent element</h1>\
</div>\
'
templateUrl(string|function)
templateUrl is optional. if provided, it must be set to either:
1. the path to an HTML file, as a string
2. a function that takes two arguments: tElement and tAttrs. The function must return the path to an HTML file as a string.
By default, the HTML file will be requested on demand via Ajax when the directive is invoked. We should bear two important factors in mind:
1. When developing locally, we should run a server in the background to serve up the local HTML templates from our file system. Failing to do so will raise a Cross Origin Request Script(CORS) error.
2. Template loading is asynchronous, which means that compilation and linking are suspended until the template is loaded.
replace(boolean)
replace is optional. If provided, it must be set to true. It is set to false by default. That means that the directive's template will be appended as a child node within the element where the directive was invoked.
Scope Option(boolean|object)
scope is optional. It can be set to true or to an object,{}. By default, it is set to false.
When scope is set to true, a new scope object is created that prototypically inherits from its parent scope.
If multiple directives on an element provide an isolate scope, only new scope is applied. Root elements within the template of a directive always get a new scope;thus, for those objects, scope is set to true by default.
The built-in ng-controller directive exists for the sole purpose of creating a new child scope that prototypically inherits from the surrounding scope. It creates a new scope that inherits from the surrounding scope.
<div ng-init="someProperty='some data'"></div>
<div ng-init="siblingProperty='more data'">
Inside Div Two: {{ aThirdProperty }}
<div ng-init="aThirdProperty='data for 3rd property'" ng-controller="SomeController">
Inside Div Three: {{ aThirdProperty }}
<div ng-init="aFourthProperty">
Inside Div Four: {{ aThirdProperty }}
</div>
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.controller('SomeController', function($scope) {
});
</script>
the output is:
Inside Div Two:
Inside Div Three: data for 3rd property
Inside Div Four: data for 3rd property
We can include the directive into the child scope:
<div ng-init="someProperty = 'some data'"></div>
<div ng-init="siblingProperty = 'more data'">
Inside Div Two: {{ aThirdProperty }}
<div ng-init="aThirdProperty = 'data for 3rd property'" ng-controller="SomeController">
Inside Div Three: {{ aThirdProperty }}
<div ng-controller="SecondController">
Inside Div Four: {{ aThirdProperty }}
<br>
Outside myDirective: {{ myProperty }}
<div my-directive ng-init="myProperty = 'wow, this is cool'">
Inside myDirective: {{ myProperty }}
</div>
</div>
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.controller('SomeController', function($scope) {
})
.controller('SecondController', function($scope) {
});
myApp.directive('myDirective', function() {
return {
restrict: 'A',
scope: true
}
})
</script>
the output is:
Inside Div Two:Inside Div Three: data for 3rd property
Inside Div Four: data for 3rd property
Outside myDirective:
Inside myDirective: wow, this is cool
Isolate Scope
To create a directive with isolate scope we'll need to set the scope property of the directive to an empty object, {}. Once we've done that, no outer scope is available in the template of the directive.
<div ng-controller="MainController">
Outside myDirective: {{ myProperty }}
<div my-directive ng-init="myProperty='wow, this is cool'">
Inside myDirective: {{ myProperty }}
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.controller('MainController', function($scope) {
})
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {},
template: '<div>Inside myDirective {{ myProperty }}</div>'
}
});
</script>
the output is:
Outside myDirective: wow, this is cool
Inside myDirective
we can see the different example:
<div ng-init="myProperty='wow, this is cool'"></div>
Surrounding scope: {{ myProperty }}
<div my-inherit-scope-directive='SomeController'>
Inside an directive with inherited scope: {{ myProperty }}
</div>
<div my-directive>
Inside myDirective, isolate scope: {{ myProperty }}
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
return {
restrict: 'A',
scope: {}
};
})
.directive('myInheritScopeDirective', function() {
return {
restrict: 'A',
scope: true
};
})
the output is:
Surrounding scope: wow, this is cool
Inside an directive with inherited scope: wow, this is cool
Inside myDirective, isolate scope: wow, this is cool
because {{myProperty}} is belong to $rootscope, so all can get it.
Transclude
transclude is optional. If provided, it must be set to true. it is set to false by default.
the example is:
<div sidebox title="Links">
<ul>
<li>First link</li>
<li>Second link</li>
</ul>
</div>
<div sidebox title="TagCloud">
<div class="tagcloud">
<a href="">Graphics</a>
<a href="">AngularJS</a>
<a href="">D3</a>
<a href="">Front-end</a>
<a href="">Startup</a>
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.directive('sidebox', function() {
return {
restrict: 'EA',
scope: {
title: '@'
},
transclude: true,
template: '<div class="sidebox">\
<div class="content">\
<h2 class="header">{{ title }}</h2>\
<span class="content" ng-transclude>\
</span>\
</div>\
</div>'
}
});
This code tells the Angular compiler that where it finds the ng-transclude directive is where it should place the content that it has captured from inside the DOM element.
Controller(string|function)
The controller option takes a string or a function. When set to a string, the name of the string is used to look up a controller constructor function registered elsewhere in our application.
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
restrict: 'A',
controller: 'SomeController'
})
myApp.controller('SomeController', function($scope, $element, $attrs, $transclude){
})
// or
myApp.directive('myDirective', function() {
restrict: 'A',
controller:
function($scope, $element, $attrs, $transclude) {
}
})
$scope: The current scope associated with the directive element.
$element: The current element directive element.
$attrs: The attributes object for the current element.
$transclude: A transclude linking function pre-bound to the correct transclusion scope.
ControllerAs(string)
The controllerAs option enables us to set a controller alias, thus allowing us to publish our controller under this name and giving the scope access to the controllerAs name.This step allows us to reference the controller from inside the view and even allows us to not need to inject $scope:
<div ng-controller="MainController as main">
<input type="text" ng-model="main.name" />
<span>{{ main.name }}</span>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.controller('MainController', function() {
this.name = 'Ari';
});
</script>
That power allows us to create dynamic objects as controllers that are isolated and easy to test.
<div my-directive>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
return {
restrict: 'A',
template: '<h4>{{ myController.msg }}</h4>',
controllerAs: 'myController',
controller: function() {
this.msg = 'Hello world'
}
}
})
require(string|array)
The require option can be set to a string or an array of strings. The string(s) contain the name of another directive. require is used to inject the controller of the required directive as the fourth parameter of the current directive's linking function.
AngularJS Life Cycle
Compile Phase
The first is called the compile phase. During the compile phase, Angular slurps up our initial HTML page and begins processing the directives we declared according to the directive definitions we've defined within our application's JavaScript.
Each directive can have a template that may contain directives, which may have their own templates. When Angular invokes a directive in the root HTML document, it will traverse the template for that directive, which itself may contain directives, each with its own template.
Once a directive and its child templates have been walked or compiled, a function is returned for the compiled template known as the template function. Before the template function for a directive is returned, however, we have the opportunity to modify the compiled DOM tree.
Once we have compiled a complete representation of a single directive, we momentarily have access to it via the compile function, whose method signature includes access to the element where the directive was declared(tElement) and other attributes provided to that element(tAttrs). This compile function returns the template function, which includes the entire parsed tree.
The main takeaway here is that because each directive may have its own template and its own compile function, each directive returns tis own template function. The top-level directive that started the chain returns the combined template function of all its children, but anywhere within that tree, we have access to just that branch via the compile function.
Compile(object|function)
The compile option can return an object or a function.
compile: function(tEle, tAttrs, transcludeFn) { var tplEl = angular.element('<div>' +
'<h2></h2>' +
'</div>');
var h2 = tplEl.find('h2'); h2.attr('type', tAttrs.type); h2.attr('ng-model', tAttrs.ngModel); h2.val("hello"); tEle.replaceWith(tplEl);
return function(scope, ele, attrs) {
// The link function
} }
The template instance and link instance may be different objects if the template has been cloned. Thus, we can only do DOM transformations that are safe to do to all cloned DOM nodes within the compile function. Don't do DOM listener registration: that should be done in the linking function.
The compile function deals with transforming the template DOM.
The link function deals with linking scope to the DOM.
Link
The link function is optional. If the compile function is defined, it returns the link function;therefore, the compile function will overwrite the link function when both are defined.