AngularJS Directive’s scope key provides us complete control over the scope of our directive element. Our goal while writing a directive should be to avoid polluting parent scope as much as possible. In this post we will discuss various scope related options/strategies including using parent scope, inheriting parent scope and creating an Isolated scope
[isolating directive’s inner scope from parent scope].
Isolated Scope in particular is very interesting in the sense that by creating Isolated scope, directive’s inner scope can be separated from outer (parent) scope. Directive’s scope does not inherit anything from parent scope. This decoupling from parent scope is important as then directive becomes independent and can be used in different situations (reusable) without relying on specific properties/functions of parent. If directive needs some input from parent scope, that can be passed by mapping directive’s inner-scope to outer-scope [with help of attributes e.g.]
Let’s understand this whole concept with help of examples. Below mentioned code is the one from previous posts (repeated here).
<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"> <h3>List of Sale Items</h3> <div ng-repeat="item in ctrl.items"> <item-widget></item-widget> </div> </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'}, ]; }]) .directive('itemWidget', [function() { return{ templateUrl:'saleItem.html', restrict: 'E' } }]); </script> </body> </html>
And the directive’s content[saleItem.html]:
<div class="panel panel-default"> <div class="panel-heading"> Published at:<span ng-bind="item.published | date"></span> </div> <div class="panel-body"> 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>
The code shown above looks nice but it have one serious flaw. It is tightly coupled to parent scope. As you can see, in directive content [saleItem.html], we are using an object ‘item’ which is coming from parent scope:
<div ng-repeat="item in ctrl.items"> <item-widget></item-widget> </div>
The ‘item’ variable is coming from ng-repeat which creates a scope for each item in controller’s array. Now if we change above ng-repeat to following, our directive will break as it really looks for ‘item’ variable in it’s content [saleItem.html].
<div ng-repeat="bla in ctrl.items"> <item-widget></item-widget> </div>
To break this coupling, we have to separate the scope inside a directive from the scope outside, make it independent, and then map the outer scope to a directive’s inner scope to get any parameter we may need in directive’s content. We can do this by creating what we call an Isolated Scope
. To do this, we can use a directive’s scope
key:
.directive('itemWidget', [function() { return{ restrict: 'E', scope: { item: '=itemInfo' }, templateUrl: 'saleItem.html', } }]);
The scope key can be passed an object [key:value pair] that contains a property for each isolated scope binding. In this case it has just one property:
Now on HTML side, we just have to map this new item-info attribute to outer’s scope.
<div ng-repeat="bla in ctrl.items"> <item-widget item-info="bla"></item-widget> </div>
Here we have mapped item-info to outer scope’s variable ‘bla’. We did not touch anything else. Directive HTML remains same as before. Now we need not to worry about name changes in outer scope as long as they are mapped to our ‘item-info’ attribute.
Live Example:
Below shown is 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"> <h3>List of Sale Items</h3> <div ng-repeat="bla in ctrl.items"> <item-widget item-info="bla"></item-widget> </div> </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'}, ]; }]) .directive('itemWidget', [function() { return{ restrict: 'E', scope: { item: '=itemInfo' }, templateUrl: 'saleItem.html', } }]); </script> </body> </html>
And the directive’s content [saleItem.html] which has not been changed at all.
<div class="panel panel-default"> <div class="panel-heading"> Published at:<span ng-bind="item.published | date"></span> </div> <div class="panel-body"> 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>
Tip:
You can further simplify it. In main HTML, in case the attribute name you provide (attribute name is item now, before it was item-info) is same as inner-scope variable name (which is item), like:
<div ng-repeat="bla in ctrl.items"> <item-widget item="bla"></item-widget> </div>
then you can simply remove the attribute name after ‘=’.
.directive('itemWidget', [function() { return{ restrict: 'E', scope: { item: '=' }, templateUrl: 'saleItem.html', } }]);
Directive’s scope key provides much more than mentioned above. In general, scope key can have following values:
Isolated scope
. With Isolated scope, directive does not inherit anything from parent. Any data that parent scope needs to share with this directive needs to be passed in using attributes. This is the best approach to create reusable & independent directives. The object passed in here, contains key that refers to directive’s attributes in HTML, and the types of values that will be passed in to the directive. Essentially, We can specify three types of values that can be passed in, which AngularJS will directly put on the scope of the directive: Let’s see the object scenario [scope : {} ] in detail with all types of options with help of an example. [Open your browser console log to see the interaction in action.]
Live Example:
Below shown is the javascript for directive.
.directive('itemWidget', [function() { return{ restrict: 'E', scope: { item: '=', promo: '@', pickMe : '&onSelect' }, templateUrl: 'saleItem3.html' } }]);
First key:value pair in scope is item : ‘=’. That means in HTML, the attribute name is item and it refers to an object which is JSON [since it is defined as ‘=’], passed from parent scope.
Second key:value pair in scope is promo : ‘@’. That means in HTML, the attribute name is promo and it refers to a String [since it is defined as ‘@’], can be passed from parent scope.
Third key:value pair in scope is pickMe : ‘&onSelect’. That means in HTML, the attribute name is on-select and it refers to a function [since it is defined as ‘&’] from a specific controller.
The HTML:
<div ng-controller="AppController as ctrl"> <h3>List of Sale Items.</h3> <div ng-repeat="bla in ctrl.items"> <item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)"></item-widget> </div> </div>
As we can see here, ‘bla’ from ng-repeat scope is a json object, assigned to item attribute. promo is assigned a simple String. on-select is assigned a function from controller which takes an argument.
Here’s the Controller’s javascript:
.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); }; }])
Finally , the template-partial [saleItem3.html] is:
<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"> 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>
In this template, we have one button which [thanks to ng-click directive] invokes pickMe from directive, passing an item name. What is important here is the key name ‘selectedItem’, which must match to the argument passed to the function in controller call in HTML.
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"> <h3>List of Sale Items.</h3> <div ng-repeat="bla in ctrl.items"> <item-widget item="bla" promo="Christmas-Sale" on-select="ctrl.onItemSelect(selectedItem)"></item-widget> </div> </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('itemWidget', [function() { return{ restrict: 'E', scope: { item: '=', promo: '@', pickMe : '&onSelect' }, templateUrl: 'saleItem3.html' } }]); </script> </body> </html>
That’s it for scope option. Let’s move to next configuration option [link function
] of Directive Definition Object, which helps us define Directive’s API and functions that can then be used by directive to preform some business logic.
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…