Angular Directives Like You Designed Them - Part 2

In this second part of directives tutorial we will examine the interplay between application data and DOM manipulation in a directive.

Written by 42ID on May 21 2015

Welcome back to the second part of Angular Directives. In the first part we saw how Angular directives work in 2 phases -Compiling phase and Linking phase. In this part, we will look at how the linking phase connects application data to DOM manipulation. As we have seen in our previous example (reproduced here for convenience), by default the link function has access to the parent scope object - in this particular case, the controller scope that surrounds the directive.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.showElement, function(value){
                        if(value){
                            element.show(); //jquery
                        }else {
                            element.hide();
                        }
                    });
        }
    };
});

In many cases, we may not want our directive to directly access our parent scope. We may either want to prototypically inherit from the parent scope or create a new isolate scope.

To create a prototypcially inherited scope, we set the scope property on the Directive Definition Object to true, like so:


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        scope: true,
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.showElement, function(value){
                        if(value){
                            element.show(); //jquery
                        }else {
                            element.hide();
                        }
                    });
        }
    };
});

In many cases, we want our directive to be stand alone components, that do not depend on the parent scope. We want them tobe able to accept parameters from external scopes, but not be tightly coupled to outside scopes. We can do this by specifying an isolate scope. Isolate scope is defined by setting the scope property on the Directive Definition object asan object.


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        scope: {},
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.showElement, function(value){
                        if(value){
                            element.show(); //jquery
                        }else {
                            element.hide();
                        }
                    });
        }
    };
});

Because we now have an isolate scope and this directive knows nothing about the parent scope it is under, we need a mechanism to send in application data from the parent scope into the directive scope. We can do this with help of attributes that we define on the element. These attributes can either map a variable from the parent scope to the directive scope or they can map an interpolated value to the directive scope. What do we mean by that? Read on!

Mapping interpolated values to directive scope

Sometime, it's sufficient to only send in data in one direction - from the parent scope to directive scope. In this case we can map an interpolated value (value evaluated by angular within {{ }} braces ) to the directive scope. We use the "@" operator on the directive scope to indicate this type of binding.


    <ng-show attribute-parent-property='{{parentProperty}}'>


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        scope: {
            directiveVariable : '@attribtueParentProperty'     
        },
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.directiveVariable, function(value){
                        if(value){
                            element.show(); //jquery
                        }else {
                            element.hide();
                        }
                    });
        }
    };
});

This establishes an one way binding between parent scope and directive scope.

Mapping a variable from parent scope to directive scope

At times, we not only want to receive the value of a property from the parent scope, but also want to be able to update it within the directive and have this change reflected in the parent scope. We use the "=" operator on the directive scope to establish such a 2 way binding:


    <ng-show attribute-parent-property='{{parentProperty}}' attribute-two-way-binding='$scope.message'>


angular.module('app')
.directive('ng-show', function(){
    // directive definition object
    return {
        scope: {
            directiveVariable : '@attribtueParentProperty'     
                directive2WayBinding: '=attributeTwoWayBinding'
        },
        link: function(scope, element, attr){
                    // LINK function
                    // can also manipulate DOM in here
                    scope.$watch(scope.directiveVariable, function(value){
                        if(value){
                            element.show(); //jquery
                            directive2WayBinding = 'The element is now visible';
                        }else {
                            element.hide();
                            directive2WayBinding = 'The element is now hidden';
                        }
                    });
        }
    };
});

Mapping to an expression on the parent scope

At time, you want want to bind to a function or an expression in the parent scope's context. A good example could be a custom event handler that is defined in the parent scope that the directive will call. Imagine having a button in a directive whose click event handler is defined in the parent scope. In this case you will use the "&" operator on the directive scope:


    <my-directive attribute-parent-function='parentFunction()'>


app.directive('myDirective', function(){
    return {
        scope: {
            localFunction : '&attributeParentFunction'
        },
        template: '<button ng-click="localFunction()">Click Event invoked from the Directive</button>'
    }
});

Was this useful?

Have questions? We have answers. Contact us, we are glad to help!

Spread the word if you found this useful! Tweet us at @thelatestbrian and @yanamegainfo

Other articles you may be interested in:

Have a topic you'll like us to cover? Send us a message on Twitter!