Shaun Xu

The Sheep-Pen of the Shaun


News

logo

Shaun, the author of this blog is a semi-geek, clumsy developer, passionate speaker and incapable architect with about 10 years’ experience in .NET and JavaScript. He hopes to prove that software development is art rather than manufacturing. He's into cloud computing platform and technologies (Windows Azure, Amazon and Aliyun) and right now, Shaun is being attracted by JavaScript (Angular.js and Node.js) and he likes it.

Shaun is working at Worktile Inc. as the chief architect for overall design and develop worktile, a web-based collaboration and task management tool, and lesschat, a real-time communication aggregation tool.

MVP

My Stats

  • Posts - 122
  • Comments - 576
  • Trackbacks - 0

Tag Cloud


Recent Comments


Recent Posts


Archives


Post Categories


.NET



Directive is very powerful in Angular.js. I would like call it "User Control for Web". When I'm writing sx.wizard, sx.highlight and sx.tabs, I was using directives. And in directive, one of the most important concept is isolated scope.

 

Shared Scope

Let's start with an example. Assuming I have an Angular.js application. I have a controller contains an object, and display the content in the page. The code is very simple.

   1: <body ng-app="Demo" >
   2:   <div class="panel panel-info" ng-controller="HomeCtrl">
   3:     <div class="panel-heading">Home Controller</div>
   4:     <div class="panel-body">
   5:         <pre>{{person | json}}</pre>
   6:  
   7:         <hr>
   8:     </div>
   9:   </div>
  10:  
  11:   <script>
   1:  
   2:     (function() {
   3:       var app = angular.module('Demo', []);
   4:  
   5:       app.controller('HomeCtrl', function($scope) {
   6:         $scope.person = {
   7:           name: 'Shaun',
   8:           age: 36,
   9:           Company: 'IGT'
  10:         };
  11:       });
  12:     }());
  13:   
</script>
  12: </body>

And below is what the page looks like.

image

Now let create a very simple directive which will show the object from the parent controller.

   1: app.directive('sxIsolatedScopeDemo', function () {
   2:   return {
   3:     templateUrl: '/tpl.html'
   4:   };
   5: });

Below is the template content of this directive.

   1: <pre>{{person | json}}</pre>
   2: <div class="form-horizontal">
   3:   <div class="form-group">
   4:     <label class="col-sm-2 control-label">Name</label>
   5:     <div class="col-sm-10">
   6:       <input type="text" class="form-control" placeholder="Name" ng-model="person.name">
   7:     </div>
   8:   </div>
   9:   <div class="form-group">
  10:     <label class="col-sm-2 control-label">Age</label>
  11:     <div class="col-sm-10">
  12:       <input type="number" class="form-control" placeholder="Age" ng-model="person.age">
  13:     </div>
  14:   </div>
  15:   <div class="form-group">
  16:     <label class="col-sm-2 control-label">Company</label>
  17:     <div class="col-sm-10">
  18:       <input type="text" class="form-control" placeholder="Company" ng-model="person.company">
  19:     </div>
  20:   </div>
  21: </div>

And now let's insert this directive into the controller we defined previously.

   1: <div class="panel panel-info" ng-controller="HomeCtrl">
   2:   <div class="panel-heading">Home Controller</div>
   3:   <div class="panel-body">
   4:       <pre>{{person | json}}</pre>
   5:  
   6:       <hr>
   7:       
   8:       <div class="panel panel-success">
   9:         <div class="panel-heading">Directive (Not Isolated Scope)</div>
  10:         <div class="panel-body">
  11:             <div sx-isolated-scope-demo></div>
  12:         </div>
  13:       </div>
  14:   </div>
  15: </div>

Well below is what the page looks like now. As you can see we can use variants defined in the parent scope of our directive. And when I changed the object content, parent scope also be changed as well.

image

This is OK for our demo, but there's a big problem. Our directive must be located in a controller where its scope contains a variant named "person". Also, anything changed against "person" inside the directive will affect the one in parent scope. As what you've seen in the screenshot above, "person" in both parent scope and directive scope are all changed when I specified new value. And even worst, if we defined some new variants in directive's scope, it will affect the scope in parent scope, which is definitively not what we want.

 

Isolated Scope

It's very simple to create an isolated scope in a directive. Below is what my directive with an isolated scope.

   1: app.directive('sxIsolatedScopeDemo', function () {
   2:   return {
   3:     scope: {},
   4:     templateUrl: '/tpl.html'
   5:   };
   6: });

Below is what it looks like now. Note that when we change something and add new variant inside the directive scope, it will NOT affect the parent scope.

ezgif.com-optimize (1)

 

Share Values in Isolated Scope

In order to make directive to be able to touch values defined in the parent scope we can specify in its scope options. Angular.js provides three ways: specify a variant, an expression, and a function.

We use "=" with attribute name to define a variant scope binding. Below I told my directive, there's an attribute named "the-person" defined in its HTML element, and the value of this attribute is the variant name defined in parent scope, which will be bind into the variant "me" inside the directive's scope.

   1: app.directive('sxIsolatedScopeDemo', function () {
   2:   return {
   3:     scope: {
   4:       me: '=thePerson'
   5:     },
   6:     templateUrl: '/tpl.html'
   7:   };
   8: });

And in the HTML part we added "the-person" attribute and point it to "person", which is the value we want to share from the parent scope.

   1: <div class="panel panel-success">
   2:   <div class="panel-heading">Directive</div>
   3:   <div class="panel-body">
   4:       <div sx-isolated-scope-demo the-person="person"></div>
   5:   </div>
   6: </div>

Also changed a little in the directive template as below.

   1: <pre>{{me | json}}</pre>
   2: <pre>$scope.foo = {{foo}}</pre>
   3:  
   4: <div class="form-horizontal">
   5:   <div class="form-group">
   6:     <label class="col-sm-2 control-label">Name</label>
   7:     <div class="col-sm-10">
   8:       <input type="text" class="form-control" placeholder="Name" ng-model="me.name">
   9:     </div>
  10:   </div>
  11:   <div class="form-group">
  12:     <label class="col-sm-2 control-label">Age</label>
  13:     <div class="col-sm-10">
  14:       <input type="number" class="form-control" placeholder="Age" ng-model="me.age">
  15:     </div>
  16:   </div>
  17:   <div class="form-group">
  18:     <label class="col-sm-2 control-label">Company</label>
  19:     <div class="col-sm-10">
  20:       <input type="text" class="form-control" placeholder="Company" ng-model="me.company">
  21:     </div>
  22:   </div>
  23:   <div class="form-group">
  24:     <label class="col-sm-2 control-label">Foo</label>
  25:     <div class="col-sm-10">
  26:       <input type="text" class="form-control" placeholder="" ng-model="foo">
  27:     </div>
  28:   </div>
  29: </div>

Now we can see that the person information was passed into the directive in "scope.me". When we change the value inside this variant it will affect the parent scope. But if we add something new in directive's scope, for example "foo", it will NOT change the parent scope.

ezgif.com-optimize (2)

Another way to share value between parent scope and directive is to use "@" + expression. In this case, Angular.js will execute the expression defined in the attribute and set the result into the scope in directive. For example, if I have a scope property named "parentFoo" defined in parent scope and I'd like my directive scope to get its value I can use the code below.

   1: app.directive('sxIsolatedScopeDemo', function () {
   2:   return {
   3:     scope: {
   4:       me: '=thePerson',
   5:       foo: '@theFoo'
   6:     },
   7:     templateUrl: '/tpl.html'
   8:   };
   9: });

Changed the directive HTML element to bind it with an expression pointing to the "parentFoo".

   1: <div class="panel panel-success">
   2:   <div class="panel-heading">Directive</div>
   3:   <div class="panel-body">
   4:       <div sx-isolated-scope-demo the-person="person" the-foo="{{parentFoo}}"></div>
   5:   </div>
   6: </div>

Below is the result. Note that when using expression binding all changes in directive scope will NOT affect to the parent scope property.

image

Expression binding allows us to generate value from parent scope properties and pass the result to directive, as below.

   1: <div sx-isolated-scope-demo 
   2:      the-person="person" 
   3:      the-foo="{{person.name}} is {{person.age}} years old working at {{person.company}}."></div>
Here is the result.
image

Sometimes we also need to invoke functions defined in the parent scope. In this case we can use "&" to tell the directive which function it will use. In the code below I added another directive scope options named "hi" which pointing to the attribute named "the-say-hi".

   1: app.directive('sxIsolatedScopeDemo', function () {
   2:   return {
   3:     scope: {
   4:       me: '=thePerson',
   5:       foo: '@theFoo',
   6:       hi: '&theSayHi'
   7:     },
   8:     templateUrl: '/tpl.html'
   9:   };
  10: });

In the page I bind it with a function in the scope named "sayHi".

   1: <div sx-isolated-scope-demo 
   2:      the-person="person" 
   3:      the-foo="{{person.name}} is {{person.age}} years old working at {{person.company}}."
   4:      the-say-hi="sayHi(exp)"></div>

And below is the function defined in parent scope, which just popup a browser alert window.

   1: app.controller('HomeCtrl', function($scope) {
   2:   $scope.person = {
   3:     name: 'Shaun',
   4:     age: 36,   
   5:     company: 'IGT'
   6:   };
   7:   $scope.parentFoo = 'bar';
   8:   $scope.sayHi = function (exp) {
   9:     alert('Hi. ' + exp);
  10:   };
  11: });

Finally in my directive template I added a button to invoke the function defined in the isolated scope, then it will find the binding function defined in parent scope. Note that how I pass parameters into the function.

   1: <button type="button" class="btn btn-default" ng-click="hi({exp: foo})">Say Hi</button>

Below is the result.

ezgif.com-optimize (3)

 

Summary

Directive is very powerful in Angular.js. It's very important to understand isolated scope when creating a reusable directive. In this post I described the different between shared scope and isolated scope. I also demonstrated how to pass data from parent scope into directive isolated scope through "=", "@" and "&", and the different between them.

 

Hope this helps,

Shaun

All documents and related graphics, codes are provided "AS IS" without warranty of any kind.
Copyright © Shaun Ziyan Xu. This work is licensed under the Creative Commons License.

Comments

Gravatar # re: Isolated Scope in Angular.js Directive
Posted by Wayne on 9/12/2017 9:22 PM
Awesome codes. Great exmples. https://clashrealm.com
Post A Comment
Title:
Name:
Email:
Comment:
Verification: