AngularJS Custom Directive’s can have controllers. Controllers in Directive’s are used for inter-directive communication. This post discusses Directive’s controller, require option and controller argument in directive’s link function. Lets get started.
In previous posts, we have discussed link function
. link function are specific to directive instance and can be used to define directive’s behavior & business logic. Controller in directive's
on the other hand are used for Directive’s inter-communication. That means, one directive on an element wants to communicate with another directive [on the same element or on parent hierarchy]. This includes sharing state or variables, or even functions.
Let’s see this with help of a live example. Play with it to see it in action. This example is built-upon example from previous posts. Complete code is available at the end of post.
Live Example:
In our example, we have one top-level directive named shoppingWidget which contains different items for sale and a shopping cart. Each Sale item itself is implemented as a directive named itemWidget. Each time an item is clicked, we add that item to shopping cart.
Below shown is itemWidget directive.
.directive('itemWidget', [function() { return{ restrict: 'E', replace:true, transclude:true, require : '^shoppingWidget', scope: { item: '=', promo: '@', pickMe : '&onSelect' }, templateUrl: 'shopItem.html', link : function(scope, element, attrs, cartCtrl){ //Add event listener for 'click' event element.on('click', function(event) { element.html('Thanks for buying this item.'); element.css({ color: 'green' }); cartCtrl.addItemToCart(scope.item);//Add item to Shopping Cart. scope.$apply(); }); } } }]);
Comparing to previous posts, there are two new concepts used in this snippet : require option
& Controller argument passed in link function
. Both concepts are related with each other.
First of all, notice the require
option in directive definition. It says to AngularJS that in order to fulfill it’s job, itemWidget requires another directives to be present in HTML. In other words, require option specifies the dependencies. A directive can be dependent on more than one directives.
If required directive [on which our directive depends on] found successfully by AngularJS , that directive’s controller will be available as the 4th argument in our directive’s link function. In case our directive is dependent on more that one directive’s, then link function gets an array of controllers as 4th argument. We will come to link function in a moment.
require option sign:
Sign preceding required directive-name provides extra meaning to AngularJS, it is explained below in detail:
Controller argument passed in link-function of a directive is the controller of the directive referred in required option. If there are more than one directives specified in require option , like require : [‘shoppingWidget’,’ngModel’], then link function gets an array of controllers as 4th argument.
link : function(scope, element, attrs, cartCtrl){ //Add event listener for 'click' event element.on('click', function(event) { element.html('Thanks for buying this item.'); element.css({ color: 'green' }); cartCtrl.addItemToCart(scope.item);//Add item to Shopping Cart. scope.$apply(); }); }
In our example, when an element gets clicked, we are calling a function on the passed controller [ cartCtrl.addItemToCart(scope.item);] and passing it the clicked item. The passed controller belongs to another directive. This way, we are able to communicate from one directive [itemWidget] to another directive [ shoppingWidget]. Now that directive can perform some business logic or do some special action.
Communication between directives is the core of controller function passed to link function.
As we know from above, Directive’s can include controllers which can then be used for inter-directive communication. Below shown is the shoppingWidget Directive used in our example:
.directive('shoppingWidget', [function() { return{ restrict: 'E', transclude: true, templateUrl:'shopDetails.html', scope: true,//Define a new Scope for this directive so that local variables don’t override anything in the parent scope. controller: ['$scope', function($scope) {//$scope refers to scope of this perticular directive instance. $scope.cart = [];//Now cart can be accessed as 'cart' in directive's template. var self = this; self.addItemToCart = function(item) {//This function can be accessed by child or sibling controllers. //This function will not be accessible from directive's template as it is not defined on scope. $scope.cart.push(item); }; }] } }])
There are several interesting things going on here.
scope : true
. It defines a new Scope for this directive so that local variables from this directive don’t override anything in the parent scope (whichever it may be). It’s a good practice.Take special note that we have also included transclude:true. Reason being that this directive acts as a container which will include other directives like itemWidget.Below shown is it’s template file [shopDetails.html]
<div> <!--Display Shopping Cart --> <div class="panel panel-success"> <div class="panel-heading"> Your Shopping Cart. </div> <div class="panel-body"> <ul class="list-group"> <li class="list-group-item" ng-show="cart.length > 0"> Total Items <span class="badge">{{cart.length}}</span> </li> <li class="list-group-item" ng-hide="cart.length > 0"> Your cart is Empty. </li> </ul> <ul class="list-group"> <li class="list-group-item" ng-repeat="item in cart"> {{item.name}} with Price {{item.price}} Quantity : </li> </ul> </div> </div> <!--Include Items on sale --> <div ng-transclude></div> </div>
First child div represents a simple shopping-cart, using ng-show/ng-hide to show/hide perticular messages, and a simple loop on $scope.cart using ng-repeat to display the item being added.
Second child div is shown with ng-transclude. This div at runtime will contain the content of the child element of shopping-widget[from HTML].
Below shown is the main HTML:
<div ng-controller="AppController as ctrl"> <shopping-widget> <!--Acting as parent container--> <div class="panel panel-success"> <div class="panel-heading"> Popular Items on Sale. </div> <div class="panel-body"> <div ng-repeat="bla in ctrl.items"> <item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)"> Last item left. Hurry up. </item-widget> </div> </div> </div> </shopping-widget> </div>
And the itemWidget’s template/HTML :
shopItem.html
<div class="panel panel-default"> <div class="panel-heading"> Published at:<span ng-bind="item.published | date"></span> Promotion: {{promo}}<button class="pull-right" ng-click="pickMe({selectedItem:item.name})">Buy me</button> </div> <div class="panel-body"> <div class="alert alert-info" ng-transclude></div> Name:<span ng-bind="item.name"></span> Condition:<span ng-bind="item.condition"></span> Price:<span ng-bind="item.price | currency"></span> Brand:<span ng-bind="item.brand"></span> </div> </div>
That’s it. This should be enough to get started with writing your own directive’s. As always, please share your thoughts and improve our collective learning process.
Complete Code
<html> <head> <title>Directive Demo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/> </head> <body class="jumbotron container" ng-app="myApp"> <div ng-controller="AppController as ctrl"> <shopping-widget> <div class="panel panel-success"> <div class="panel-heading"> Popular Items on Sale. </div> <div class="panel-body"> <div ng-repeat="bla in ctrl.items"> <item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)"> Last item left. Hurry up. </item-widget> </div> </div> </div> </shopping-widget> </div> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.js"> </script> <script> angular.module('myApp', []) .controller('AppController', [function() { var self = this; self.items = [ {name: 'Computer', price: 500, condition:'New',brand : 'Lenovo', published:'01/11/2015'}, {name: 'Phone', price: 200, condition:'New',brand : 'Samsung', published:'02/11/2015'}, {name: 'Printer', price: 300, condition:'New',brand : 'Brother', published:'06/11/2015'}, {name: 'Dishwasher', price: 250, condition:'Second-Hand',brand : 'WhirlPool', published:'01/12/2015'}, ]; self.onItemSelect = function(name) { console.log('Congrats. You have just bought a', name); }; }]) .directive('shoppingWidget', [function() { return{ restrict: 'E', transclude: true, templateUrl:'shopDetails.html', scope: true,//Define a new Scope for this directive so that local variables don’t override anything in the parent scope. controller: ['$scope', function($scope) {//$scope refers to scope of this perticular directive instance. $scope.cart = [];//Now cart can be accessed as 'cart' in directive's template. var self = this; self.addItemToCart = function(item) {//This function can be accessed by child or sibling controllers. //This function will not be accessible from directive's template as it is not defined on scope. $scope.cart.push(item); }; }] } }]) .directive('itemWidget', [function() { return{ restrict: 'E', replace:true, transclude:true, require : '^shoppingWidget', scope: { item: '=', promo: '@', pickMe : '&onSelect' }, templateUrl: 'shopItem.html', link : function(scope, element, attrs, cartCtrl){ //Add event listener for 'click' event element.on('click', function(event) { element.html('Thanks for buying this item.'); element.css({ color: 'green' }); cartCtrl.addItemToCart(scope.item);//Add item to Shopping Cart. scope.$apply(); }); } } }]); </script> </body> </html>
shopItem.html
<div class="panel panel-default"> <div class="panel-heading"> Published at:<span ng-bind="item.published | date"></span> Promotion: {{promo}}<button class="pull-right" ng-click="pickMe({selectedItem:item.name})">Buy me</button> </div> <div class="panel-body"> <div class="alert alert-info" ng-transclude></div> Name:<span ng-bind="item.name"></span> Condition:<span ng-bind="item.condition"></span> Price:<span ng-bind="item.price | currency"></span> Brand:<span ng-bind="item.brand"></span> </div> </div>
shopDetails.html
<div> <!--Display Shopping Cart --> <div class="panel panel-success"> <div class="panel-heading"> Your Shopping Cart. </div> <div class="panel-body"> <ul class="list-group"> <li class="list-group-item" ng-show="cart.length > 0"> Total Items <span class="badge">{{cart.length}}</span> </li> <li class="list-group-item" ng-hide="cart.length > 0"> Your cart is Empty. </li> </ul> <ul class="list-group"> <li class="list-group-item" ng-repeat="item in cart"> {{item.name}} with Price {{item.price}} Quantity : </li> </ul> </div> </div> <!--Include Items on sale --> <div ng-transclude></div> </div>
That’s it. This should be enough to get started with writing your own directive’s. As always, please share your thoughts and improve our collective learning process.
You can use popular Apache HTTP server to serve files. If you need assistance setting-up server,
this post can help you.
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…
View Comments
helpful and informative!