AngularJS-TechTalks / directives

33

Transcript of AngularJS-TechTalks / directives

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.