Angular custom directives

What are Directives?

  • markers on a DOM element (attribute, element name, comment or CSS class);
  • "... a way to tech HTML new tricks.";
  • anything (within your app) that should touch DOM;
  • a function that's attached to a DOM element:
    • a whole execution environment;
    • a micro-application;
<!-- Built in directive -->
<div ng-repeat="item in items"> </div>

<!-- Custom directive -->
<my-directive> </my-directive>

<!-- OR -->
<div my-directive> </div>

Use cases

  • event handling;
  • behavior management;
  • template pre-processing and insertion;
  • data-binding;
  • UI Widgets;
  • much more.

Why a directive?

  1. Declarative, Model-Driven Behaviour
  2. Modularity, Reusability across contexts
  3. Keep it local
<!-- Imperative Way -->
<button id="1" class="B C"></button>

<!-- V.s. Declarative way-->

<button my-action-handler></button>

Building a Directive

<my-directive> </my-directive>
.directive('myDirective', function(){

});

Directive Names Angular uses a convention borrowed from other JS projects: names in HTML are hyphenated

while identifiers in the JS are camel-cased

Building a Directive

angular
    .module('myApp', [])
    .directive('myDirective', function(){
        return{
            link: function(scope, lement, attrs, ctrl){
                
                element.bind("mouseenter", function(){
                    element.css({color: 'red'});
                });
            },
            require: '',
            controller: ['$scope', '$element', function($scope, $element){}],
            scope: false,
            transclude: false,
            restrict: 'E',
            template: "<div>Hello, World!</div>"
        };
    });

Everything but link is optional.

Link Function Args

The params are supplied as args not injected:

  • scopescope associated with the directive
  • elementDOM element wrapped by jQLite
  • attrsan object containing the html attributes defined on the element
  • ctrlReference to controller object

Directives Template

  • Templates can be stored as strings on the template property;
  • They can also be loaded from a file, using templateUrl property

Restrict property

  • Directives can be restricted to a specific context, so the accidentally usage can be avoided:
    • E - Element
    • A - Attribute
    • C - Class
  • Can be used as a single string: 'EAC';
  • Defaults to 'A'

Directive Scope

We have the option, in directives, of using either:

  • the same scope (scope from the environment that directive lives) 
  • a child scope that inherits from current scope
  • a new isolate scope
.directive('myDirective', function(){
    return {
        //scope: false // default, use local scope
        //scope: true // create a child of local scope
        //Create a new isolate scope
        scope: {
        

        }
    }
});

Isolate Scope

Angular provides ways to bind the value of properties in isolate scope to attributes on the element, using special operators:

  • @ (or @attr) - Local scope property
  • = (or =attr) - Bi-directional binding
  • & (or &attr) - Parent execution binding
scope: {
    local1: '@attr1',
    local2: '=attr1',
    local3: '&attr3'
}

Example

Transclusion

  • Transclude allow to pass in an entire template, including its scope, to a directive.
  • In order for scope to be passed in, the scope option must be isolated, {}, or set to true.
<div sidebox title="Links">
    <ul>
        <li>First link</li>
        <li>Second link</li>
    </ul>
</div>

<script type="text/ng-template" id="directive.template.html">
    <h2 class="header">{{ title }}</h2>
    <div class="content" ng-transclude></div>
</script>
angular.module('myApp', [])
    .directive('sidebox', function() {
        return {
            restrict: 'EA',
            scope: {
                title: '@'
            },
            transclude: true,
            templateUrl: 'directive.template.html'
    });

Directive Controller

  • The main use case for a controller is when we want to provide reusable behavior between directives
  • As the link function is only available inside the current directive, any behavior defined within is not shareable
  • Because a directive can require the controller of another directive, controllers are a good choice for placing actions that will be used by multiple directives.

The link function provides isolation between directives, while the controller defines shareable behavior.

Require option

  • 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 provide the controller of the required directive as the fourth parameter of the current directive’s linking function

The string(s) provided to the require option may optionally be prefixed with the following options:

  • ?  - If the required controller is not found on the directive provided, pass null
  • ^  - look upwards on its parent chain for the controller in the require option
  • optionally require the controller and look up the parent chain for the controller

How directives are processed in AngularJS

The Code

<level-one>  
    <level-two>
        <level-three>
            Hello {{name}}         
        </level-three>
    </level-two>
</level-one>  
function createDirective(name){  
  return function(){
    return {
      restrict: 'E',
      compile: function(tElem, tAttrs){
        console.log(name + ': compile => ' + tElem.html());
        return {
          pre: function(scope, iElem, iAttrs){
            console.log(name + ': pre link => ' + iElem.html());
          },
          post: function(scope, iElem, iAttrs){
            console.log(name + ': post link => ' + iElem.html());
          }
        }
      }
    }
  }
}

angular.module('myModule', [])
    .directive('levelOne', createDirective('levelOne')) 
    .directive('levelTwo', createDirective('levelTwo'))
    .directive('levelThree', createDirective('levelThree'));  

The goal is simple: let AngularJS process three nested directives where each directive has its own compile, pre-link and post-link function that logs a line to the console so we can identify them.

What's happening?

Compile

  1. Angular start to process the DOM when it detects that the DOM is ready;
  2. it bumps into the <level-one> element and knows from its directive definition that some action needs to be performed:
    1. ​Because a compile function is defined in the levelOne directive definition object, it is called and the element's DOM is passed as an argument to the function;
    2. Once the compile function of the levelOne directive has run, Angular recursively traverses deeper into the DOM and repeats the same compilation step for the <level-two> and <level-three> elements.

Post-link (alias Link fn)

  1. After Angular travels down the DOM and has run all the compile functions, it traverses back up again and runs all associated post-link functions.
  2. The DOM is now traversed in the opposite direction and thus the post-link functions are called in reverse order. 
  3. This reverse order guarantees that the post-link functions of all child elements have run by the time the post-link function of the parent element is run.
  4. Once Angular has called the compile function of a directive, it creates an instance element of the template element and provides a scope for the instance. 
  5. So by the time the linking occurs, the instance element and scope are already available and they are passed by Angular as arguments to the post-link function.

Post-link (alias Link fn)

Pre-link

  1. Angular provides a way to run code before any of the child element's post-link functions have run;
  2. Angular calls pre-link in the original order;
  3. A pre-link function of an element is guaranteed to run before any pre-link or post-link function of any of its child elements.

Compile:

Use only to change the original DOM (template element) before Angular creates an instance of it and before a scope is created.

 

Pre-link:

Use to implement logic that runs when Angular has already compiled the child elements, but before any of the child element's post-link and pre-link functions have been called.

 

Post-link:

Use to execute logic, knowing that all child elements have been compiled and all pre-link and post-link functions of child elements have been executed.

Summary

Full Article

Demo

Credits

Angular custom directives

By Alexe Bogdan

Angular custom directives

Angular custom directives

  • 1,016