DIRECTIVE DEFINITIONThe simplest way to think about a directive is that it is simply a function that we run on a
particular
DOM element. The function is expected to provide extra functionality on the element.
For instance, the ng-click directive gives an element the ability to listen for the click event andrun
an Angular expression when it receives the event. Directives are what makes the Angularframework
so powerful, and, as we’ve seen, we can also create them.
A directive is defined using the .directive() method, one of the many methods available on our
application’s Angular module.
angular.module('myApp' )
.directive('myDirective' ,function ($timeout, UserDefinedService) {
//directive definition goes here
});
The directive() method takes two arguments:
DIRECTIVE DEFINITION
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 shouldbehave when it is invoked in the DOM.
angular.application('myApp' , [])
.directive('myDirective' , function () {
// A directive definition object
return {
// directive definition is defined via options
// which we'll override here
};
});
DIRECTIVE DEFINITIONWe can also return a function instead of an object to handle this directive definition, but it is
best
practice to return an object as we’ve done above. When a function is returned, it is often referred to
as the postLink function, which allows us to define the link function for the directive. Returning
a function instead of an object limits us to a narrow ability to customize our directive and, thus, is
good for only simple directives.
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.
<div my-directive></div>
DIRECTIVE DEFINITIONLet’s look at all the available options we can provide to a directive definition:
Restrict (string)
restrict is an optional argument. It is responsible for telling Angular in which format our
directive
will be declared in the DOM. By default, Angular expects that we will declare a custom directive
as
an attribute, meaning the restrict option is set to A.
The available options are as follows:
• E (an element) {lang=”html”} <my-directive></my-directive>
• A (an attribute, default) {lang=”html”} <div my-
directive=”expression”></div>
• C (a class) {lang=”html”} <div class=”my-directive: expression;”></div>
• M (a comment) {lang=”html”} <– directive: my-directive expression –>
DIRECTIVE DEFINITIONThese options can be used alone or in combination:
angular.module('myApp')
.directive('myDirective', function() {
return {
restrict: 'EA' // either an element or an attribute
};
});
In this case, we can declare the directive as an attribute or an element:
<-- as an attribute -->
<div my-directive></div>
<-- or as an element -->
<my-directive></my-directive>
DIRECTIVE DEFINITION
Priority (number):
The priority option can be set to a number. Most directives omit this option, in which case it
defaults
to 0, however, there are cases where setting a high priority is important and/or necessary. For
example, ngRepeat sets this option at 1000 so that it always gets invoked before other
directives
on the same element.
If an element is decorated with two directives that have the same priority, then the first directive
declared on the element will be invoked first. If one of the directives has a higher priority than
the
other, then it doesn’t matter which is declared first: The one with the higher priority will always
run first.
DIRECTIVE DEFINITION
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.
As a result, don’t further decorate an element if it’s already been decorated with a directive that
is
terminal and has equal or higher priority – it won’t be invoked.
<div id="test" myDirective="33" secondDirective="44"></div>
DIRECTIVE DEFINITIONTemplate (string|function): template is optional. If provided, it must be set to either:
• a string of HTML
• a function that takes two arguments – tElement and tAttrs – and returns a string value.
Angular treats the template string no differently than any other HTML.
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: function(tElem, tAttrs){
var 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>\’;
return 'string to use as template';
}
}
DIRECTIVE DEFINITION
templateUrl (string|function):
templateUrl is optional. If provided, it must be set to either:
• the path to an HTML file, as a string
• a function that takes two arguments: tElement and tAttrs. The function must return the path
to an HTML file as a string.
In either case, the template URL is passed through the built-in security layer that Angular
provides; specifically $getTrustedResourceUrl, which protects our templates from being
fetched
from untrustworthy resources.
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:
DIRECTIVE DEFINITION• 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.
• Template loading is asynchronous, which means that compilation and linking are suspended
until the template is loaded.
After a template has been fetched, Angular caches it in the default $templateCache services.
In
production, we can pre-cache these templates into a JavaScript file that defines our templates.
So we don’t have to fetch the templates over XHR.
{
templateUrl: 'some.url'
}
//Hint OSX – linux users from terminal:
open /Applications/Google\ Chrome.app --args --allow-running-insecure-content --disable-web-security
DIRECTIVE DEFINITIONreplace (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 if is true will replace the node by template by which was invoked .
<div some-directive></div>
<div id=“some_directive_div”>
<h1>I am replaced</h1>
</div>
.directive('someDirective' function () {
return {
replace: true // MODIFIED
template: '<div>some stuff here<div>'
}
});
DIRECTIVE DEFINITION
Transclude (boolean):
Transclude is optional. If provided, it must be set to true. It is set to false by default. That means that
Everything that is provided in directive markup, will be cut from initial template and put in the place where ng-transclude is invoked
<div some-directive>
<h1>I am replaced</h1>
</div>
.directive('someDirective' function () {
return {
replace: true // MODIFIED
transclude: true,
template: '<div ng-transclude>some stuff here<!—h1 will go here--><div>'
}
});
DIRECTIVE DEFINITION
scope: boolean or object:
In order to fully understand the rest of the options available inside a directive definition object,
we’ll
need to have an understanding of how scope works.
A special object, known as the $rootScope, is initially created when we declare the ng-app
directive
in the DOM:
<div ng-app="myApp” ng-init="someProperty = 'some data'”><!--$rootScope-->
<div ng-init="siblingProperty = 'more data'"><!--$scope-->
Inside Div Two
</div>
<div ng-init="aThirdProperty"></div>
</div>
DIRECTIVE DEFINITIONIn the markup above the div that has attribute value “someproperty” is the $rootScope
cause it is
initialized by the ng-app.
Second div with attribute value “siblingProperty” is a child scope of the $rootScope as
it is the
Child element of the parent div.
Third div with attribute value “thirdProperty” is a child scope of the $rootScope again,
and this way
down to whole prototype chain.
In the code above, we set three properties on the root scope of our app: someProperty,
siblingProperty, and anotherSiblingProperty.
From here on out, every directive invoked within our DOM will:
• directly use the same object,
• create a new object that inherits from the object, or
• create an object that is isolated from the object
DIRECTIVE DEFINITIONScope 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. 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.
Let’s amend our last example, with this knowledge in hand:
<div ng-app="myApp" ng-init="someProperty = 'some data'">
<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>
</div>
DIRECTIVE DEFINITIONangular.module('myApp' , [])
.controller('SomeController' , function ($scope) {
// we can leave it empty, it just needs to be defined
});
If we reload the page, we can see that inside the second div, {{ aThirdProperty }} is undefined
and therefore outputs nothing. Inside the third div, however, the value we set inside our inherited
scope data for a 3rd property is shown.
To create our own directive whose scope prototypically inherits from the outside world, set the scope true
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: true //setting this to false just will use the scope on which the element is called
}
});
DIRECTIVE DEFINITIONOk now lets see the following example:
<div ng-app="myApp" ng-init="someProperty = 'some data'">
<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>
</div>
DIRECTIVE DEFINITION
Isolate Scope: Isolate scope is likely the most confusing of the three options available when setting the scope
property, but also the most powerful. Isolate scope is based on the ideology present in Object
Oriented Programming. Languages like Small Talk and design principles like SOLID have
found
their way into Angular via directives that use 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:
Outside myDirective: {{ myProperty }}
<div ng-controller="testCtrl">
<div my-directive><div>
</div>
DIRECTIVE DEFINITION<script>
angular.module('myApp', [])
.controller('testCtrl', function($scope) {
$scope.myProperty = "Hello world";
})
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {},
template: "Inside myDirective: {{ myProperty }}"
};
})
</script>
In this case we do not see the {{myProperty}} and output, because scope is isolate {} empty obj
DIRECTIVE DEFINITIONThe Scope is now isolated from outside world so does not inherit anything from the outside
controller.
What we should know about isolate scope ?
One way binding referencing:
This happened when we do not want to change data from directive to parent scope, but just refer it
whenever it changes . ‘@’
The following will output Inside myDirective: Hello, and be one-directional data binding (only expr, strings)
We call this one way binding because with this technique you can only pass strings to the attribute (using
expressions, {{}}).
<input type="text" ng-model="test" />
<div id=“test” my-directive title=“{{test}}”></div>
scope: {
title: ‘@’,
//or title : ‘@title’
}
template: "Inside myDirective: {{title}}",
DIRECTIVE DEFINITIONTwo way Data binding happens when we want to change some value on the parent element
and
Back Use ‘=‘ for Two Way Binding, works with objects, expressions, anything we can pass:
<div my-directive title=”testObj"><div>
.controller(‘testCtrl”, function() {
$scope.testObj = {
‘name’: ‘hello’,
‘title’: ‘world”
}
})
scope {
title: ‘=‘
},
template: ‘"Inside myDirective:{{title.name}}",’ //outputs hello, if you change object here, it will be
Changed in parent scope also
DIRECTIVE DEFINITIONFunction referrence: ‘&’:<div my-directive color="white" todo="sayHello(some)"><div>scope: {
.controller('testCtrl', function($scope) {
$scope.sayHello = function(some) {
alert(some);
}
})
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {
todo:'&',
color:'@'
},
template: "<button ng-click='todo({some:color})'>Click</button>”,
};
})
DIRECTIVE DEFINITIONController: {string}
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:
return {
controlles: ‘SomeController’,
scope {
}
}
A controller can be defined inline within a directive by setting the controller function as an
anonymous constructor function:
controller: function ($scope, $element, $attrs, $transclude) {
// controller logic goes here
}
DIRECTIVE DEFINITIONAs the above examples suggest, the arguments that we can pass into a controller are:
$scope
The current scope associated with the directive element.
$element
The current element directive element.
$attrs
The attributes object for the current element. For instance, the following element:
<div id="aDiv" class="box"></div>
has the attribute object of:
{
id: "aDiv",
class: "box"
}
DIRECTIVE DEFINITIONrequire (string|array):
{
restrict: 'EA' ,
require: 'ngModel’
}
This directive definition will only look for the ng-model="" definition in the local scope of the
directive.
<!-- Our directive will find the ng-model on the local scope -->
<div my-directive ng-model="object"></div>
DIRECTIVE DEFINITIONAngularJS Life Cycle:Before our Angular application boots, it sits in our text editor as raw HTML. Once we’ve booted
the
app, and the compile and link stages have taken place, however, we’re left with a live data-bound
application that responds on the fly to changes made by the user on the scope to which our HTML
is bound. How does this magic take place, and what do we need to know in order to build effective
applications?
There are two main phases that take place:
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.
DIRECTIVE DEFINITIONThe compile option can return an object or a function.
Understanding the compile vs link option is one of the more advanced topics we’ll run across in
Angular, and it provides us with considerable context about how Angular really works.
The compile option by itself is not explicitly used very often; however, the link function is used
very often. Under the hood, when we set the link option, we’re actually creating a function that will
define the post() link function so the compile() function can define the link function.
compile: function (tEle, tAttrs, transcludeFn) {
var tplEl = angular.element('<div></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
}
}
DIRECTIVE DEFINITIONLink:
We use the link option to create a directive that manipulates the DOM.
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. If our directive is
simple
and doesn’t require setting any additional options, we may return a function from the factory
function (callback function) instead of an object. When doing so, this function will be the link
function.
These two definitions of the directive are functionally equal:
DIRECTIVE DEFINITIONangular.module('myApp' [])
.directive('myDirective' , function () {
return {
pre: function (tElement, tAttrs, transclude) {
// executed before child elements are linked
// NOT safe to do DOM transformations here b/c the `link` function
// called afterward will fail to locate the elements for linking
},
post: function (scope, iElement, iAttrs, controller) {
// executed after the child elements are linked
// IS safe to do DOM transformations here
// same as the link function, if the compile option here we're
// omitted
}
}
});
DIRECTIVE DEFINITIONlink: function (scope, ele, attrs) {
return {
pre: function (tElement, tAttrs, transclude) {
// executed before child elements are linked
// NOT safe to DOM transformations here b/c the `link` function
// called afterward will fail to locate the elements for linking
},
post: function (scope, iElement, iAttrs, controller) {
// executed after the child elements are linked
// IS safe to do DOM transformations here
// same as the link function, if the compile option here we're
// omitted
}
}
DIRECTIVE DEFINITIONIf the directive definition has been provided with the require option, the method signature will
gain
a fourth argument representing the controller or controllers of the required directive:
require 'SomeController',
link: function (scope, element, attrs, SomeController) {
// manipulate the DOM in here, with access to the controller of the
// required directive
}
If the require option was given an array of directives, the fourth argument will be an array
representing the controllers for each of the required directives.
Let’s go over each of the arguments available to the link function:
DIRECTIVE DEFINITIONscope
The scope to be used by the directive for registering watches from within the directive.
iElement
The instance element is the element where the directive is to be used. We should only manipulate
children of this element in the postLink function, since the children have already been linked.
iAttrs
The instance attributes are a normalized (pascalCased) list of attributes declared on this element
and are shared between all directive linking functions. These are passed as JavaScript objects.
controller
The controller argument points to the controller that’s defined by the require option. If there is
no require option defined, then this controller argument is set as undefined.
The controller is shared among all directives, which allows the directives to use the controllers as
a communication channel (public API). If multiple requires are set, then this will be an array of
controller instances, rather than just a single controller.