Shaun Xu

The Sheep-Pen of the Shaun



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.


My Stats

  • Posts - 109
  • Comments - 420
  • Trackbacks - 0

Tag Cloud

Recent Comments

Recent Posts


Post Categories


Bootstrap provides a component named Tabs, which provides tab container and tab pages. In order to make it easy to use in Angular.js, Angular UI team created a directive called "ui.bootstrap.tabs". By using this directive we can define a bootstrap tab component, we can also specify the content in each tab pages. But the problem is, all tabs' content must be in the same scope. For example, below is the sample code from "ui.bootstrap.tabs". As we can see, I must put all tabs' content in the same page, and using the same scope.

   1: <tabset>
   2:   <tab heading="Static title">Static content</tab>
   3:   <tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="" disable="tab.disabled">
   4:     {{tab.content}}
   5:   </tab>
   6:   <tab select="alertMe()">
   7:     <tab-heading>
   8:       <i class="glyphicon glyphicon-bell"></i> Alert!
   9:     </tab-heading>
  10:     I've got an HTML heading, and a select callback. Pretty cool!
  11:   </tab>
  12: </tabset>


In my project, I would like to have a page with a tab container, but each tab's template (HTML) and business logic (controller) can be defined separately. Something looks like this.

   1: <tabset>
   2:     <tab template="tabs/tab-1.html" controller="Tab1Ctrl"></tab>
   3:     <tab template="tabs/tab-2.html" controller="Tab2Ctrl"></tab>
   4:     <tab template="tabs/tab-3.html" controller="Tab3Ctrl"></tab>
   5: </tabset>

And this component should be smart enough to load content and execute business logic when user active this tab. So this is the reason I created this directive.



- Tab's content can be defined through inline HTML string or an external HTML file.

- Tab's controller can be specified inline or by name.

- Each tab contains its own scope, but can share objects through "$scope.$" property.

- Tabs can be close or open.

- Tab's content and logic is lazy load (render and execute only when the tab was shown).

- Provides some handlers that can be hooked when a tab was opened, closed, entered and left.


Installation & 1st Tab

First you need to reference tow files from GitHub.


Then you need to reference some basic libraries I think you have done before.

- jQuery

- Bootstrap

- Angular.js


In your Angular.js application module, specify the dependency of "sx.tabs" as below.

   1: = window.angular.module('Demo', [
   4:     ... ...
   6:     'sx.tabs'
   7: ]);

And now you can define tabs in your page as below.

   1: <div sx-tabs="tabs" 
   2:      sx-tabs-context="context" 
   3:      sx-tabs-enabled="onTabEnabled(tab)" 
   4:      sx-tabs-disabled="onTabDisabled(tab)" 
   5:      sx-tab-switched="onTabSwtiched(tab)">
   6: </div>

The meaning of the attributes are described as following.

- sx-tabs: Tabs' definition. Details will be discussed later.

- sx-tabs-context: Data will be passed into each tabs' controller scope. It will be shared between all tabs.

- sx-tabs-enabled: A function will be invoked when a tab was shown from the "more" button.

- sx-tabs-disabled: A function will be invoked when a tab was hidden into the "more" button.

- sx-tabs-switched: A function will be invoked when a tab was activated.


Next, you need to define tabs in your controller scope, with the property name specified in previous step. In this case it's "$scope.tabs". In the code below I defined a tab with ID = "general".

   1: var tabs = {
   2:     'general': {
   3:         id: 'general',
   4:         title: 'General',
   5:         order: 1110,
   6:         enabled: true,
   7:         templateUrl: 'tabs/general.html',
   8:         controller: 'sxTabGeneralCtrl'
   9:     }
  10: };
  11: $scope.tabs = tabs;

Each tab definition must contain properties as below.

- id: A unique string to identify this tab in this tab container.

- title: Tab title that will be displayed in the tab bar.

- order: Defines the display sequence of tabs. Tab with lower order will be displayed on the left.

- enabled: Tab will be shown by default when it's true, otherwise it will be hidden in "more" button.

- templateUrl: HTML path for the tab content.

- template: Inline HTML string for the tab content. This value will override "templateUrl".

- controller: Inline function or controller name of this tab. This property is optional.


In "sx.tabs" each tab has its own controller and scope, that you can specify your own business logic in each tab without messing them up. In this tab we defined "$scope.productName" in controller.

   1: (function () {
   2:'sxTabGeneralCtrl', function ($scope) {
   3:         $scope.productName = 'sx.tabs';
   4:     });
   5: }())

And display in "tabs/general.html".

   1: <div class="jumbotron" style="background-color: #fff;">
   2:   <h1>Hello, tabs!</h1>
   3:   <p>Welcome to use "{{productName}}" in Shaun's Angular Toolkits</p>
   4: </div> 

Now the tab will look like this. It contains one tab with almost no logic.



Shared Data

As I mentioned above, you can pass objects into tabs and it will be shared across all tabs. To do this, you need to define the data in parent controller scope and pass through the "sx-tabs-context" attribute. For example below is the data.

   1: var context = {
   2:     user: 'Shaun',
   3:     company: 'Worktile Inc.',
   4:     department: 'Web'
   5: };
   6: $scope.context = context; 

And specify the tab context through its directive attribute named "sx-tabs-context". Then I can retrieve this object from tab's controller scope through "$scope.$". For example below I created another tab.

Note that in this tab I didn't specify controller, since it doesn't need any logic.

   1: var tabs = {
   2:     'general': {
   3:         ... ...
   4:     },
   5:     'shared-data': {
   6:         id: 'shared-data',
   7:         title: 'Shared Data',
   8:         order: 1120,
   9:         enabled: true,
  10:         templateUrl: 'tabs/shared-data.html'
  11:     }
  12: }

And in the template HTML and retrieve my shared data from "$scope.$" and bind with several inputs.

   1: <form>
   2:   <div class="form-group">
   3:     <label for="user">User</label>
   4:     <input type="text" class="form-control" id="user" placeholder="User" ng-model="$">
   5:   </div>
   6:   <div class="form-group">
   7:     <label for="company">Company</label>
   8:     <input type="text" class="form-control" id="company" placeholder="Company" ng-model="$">
   9:   </div>
  10:   <div class="form-group">
  11:     <label for="dept">Department</label>
  12:     <input type="text" class="form-control" id="dept" placeholder="Department" ng-model="$">
  13:   </div>
  14: </form>

Now we have two tabs and in the second one, you can see the data I defined in parent scope was passed into tab scope.


And since the are referring the same object, any changes inside one tab will affect all other tabs as well as the parent scope.



Tab Entering & Leaving

A tab will be rendered and performs its controller when the first time it was shown, no matter by clicking the tab title, or from the "more" button. This enhance the performance of the whole tab container. But it will never be rendered again.

In some cases we need the tab to load some data and update the page when it's switched. What we can do is to implement our own tab entering function.

To show this feature I created another tab, which was similar as the previous one.

   1: var tabs = {
   2:     'general': {
   3:         ... ...
   4:     },
   5:     'shared-data': {
   6:         ... ...
   7:     },
   8:     'enter-validate': {
   9:         id: 'enter-validate',
  10:         title: 'Load & Validation',
  11:         order: 1150,
  12:         enabled: true,
  13:         templateUrl: 'tabs/enter-validate.html',
  14:         controller: 'sxTabEnterValidateCtrl'
  15:     }
  16: }

In the controller I defined a function inside "$scope.$context.behavior" called "entering". It will be invoked when this tab was activated, with two arguments "options", and "callback".

The "options" contains information about this entering event as below.

- fromTabId: The ID of which tab switched from. It might be null if this is the first visible tab.

- entered: Indicates whether this tab had been shown before. It can be used to avoid loading data duplicated.

The "callback" argument is a function that MUST be invoked when entering logic was finished, regardless if it's success or failed.

Below is the code I implemented in my tab. It utilizes "$timeout" to simulate async loading. It also checked "options.entered" to ensure load data only when this tab was entered first time.

   1: (function () {
   2:'sxTabEnterValidateCtrl', function ($scope, $timeout) {
   3:         $scope._entering = false;
   5:         $scope.$context.behavior.entering = function (options, callback) {
   6:             $scope._entering = true;
   7:             if (options.entered) {
   8:                 $scope._entering = false;
   9:                 return callback ();
  10:             }
  11:             else {
  12:                 $timeout(function () {
  13:                     $scope._entering = false;
  14:                     return callback();
  15:                 }, 1000);
  16:             }
  17:         };
  18:     });
  19: }())


Similar as entering handler, we can implement "$scope.$context.behavior.leaving" to perform our own logic when a tab is going to be left. This could be useful if we want to prevent user from leaving this tab if any inputs were invalid.

"leaving" function also contains "options" and "callback" arguments. The details of "options" is

- toTabId: The ID of which tab it will be switched to.

- byTabDisable: A boolean value to indicates whether this leaving is because user hide this tab.

Same as "entering", "callback" function MUST be invoked when leaving logic was finished. It contains one arguments to indicates if it can be left or not. For example, if validation was failed, we need to call "return callback(false)" to prevent user leaving this tab.

Below is the code I implemented in the sample tab. I utilizes Angular.js form validation module to check if user can left this tab.

   1: $scope.$context.behavior.leaving = function (options, callback) {
   2:     $scope._leaving = true; 
   4:     if (options.byTabDisable) {
   5:         $scope._leaving = false;
   6:         return callback(true);
   7:     }
   8:     else {
   9:         $timeout(function () {
  10:             $scope._leaving = false;
  11:             return callback($scope.inputForm.$valid);
  12:         }, 1000);
  13:     }
  14: };

Now if you clear any inputs in this tab you cannot switch to any other tabs.



Show & Hide Tabs

Tabs can be hidden by clicking its close icon and can be shown again from the "more" button.


We can also define a tab which will be hidden by default. Just set its "enabled" property to "false". For example in the code below I defined 3 tabs with "enabled: false".

   1: var tabs = {
   2:     'general': {
   3:         ... ...
   4:     },
   5:     'shared-data': {
   6:         ... ...
   7:     },
   8:     'enter-validate': {
   9:         ... ...
  10:     },
  11:     'more-tabs-1': {
  12:         id: 'more-tabs-1',
  13:         title: 'More Tabs 1',
  14:         order: 1210,
  15:         enabled: false,
  16:         template: '<h1>More Tabs 1</h1>'
  17:     },
  18:     'more-tabs-2': {
  19:         id: 'more-tabs-2',
  20:         title: 'More Tabs 2',
  21:         order: 1220,
  22:         enabled: false,
  23:         template: '<h1>More Tabs 2</h1>'
  24:     },
  25:     'more-tabs-3': {
  26:         id: 'more-tabs-3',
  27:         title: 'More Tabs 3',
  28:         order: 1230,
  29:         enabled: false,
  30:         template: '<h1>More Tabs 3</h1>'
  31:     }
  32: };

Then it will be hidden.



You can also hook the events when user show, hide a tab, as well as when a tab was switched. This might be useful if you want to save the tab status into user's preference so that next time user can see tabs she opened previously.

To accomplish just specify which function these events will invoke.

   1: <div sx-tabs="tabs" 
   2:      sx-tabs-context="context" 
   3:      sx-tabs-enabled="onTabEnabled(tab)" 
   4:      sx-tabs-disabled="onTabDisabled(tab)" 
   5:      sx-tab-switched="onTabSwtiched(tab)">
   6: </div>

And implement those functions in your parent scope as below.

   1: $scope.onTabEnabled = function (tab) {
   2:     $scope.messages.push('Tab enabled: ' +;
   3: };
   4: $scope.onTabDisabled = function (tab) {
   5:     $scope.messages.push('Tab disabled: ' +;
   6: };
   7: $scope.onTabSwtiched = function (tab) {
   8:     $scope.messages.push('Tab switched: ' +;
   9: };

Then in the page you can see events fired from the directive. You can hook any of them and perform you logic.



Promise Support

The last feature I would like to mention, this directive support specify "tabs" and "context" as a promise. This is very useful when the tab definition and shared data must be retrieved asynchronous.

For example, I utilizes "$timeout" to simulate the case that retrieve "context" asynchronous.

   1: var context = {
   2:     user: 'Shaun',
   3:     company: 'Worktile Inc.',
   4:     department: 'Web'
   5: };
   6: $scope.context = $q(function (resolve, reject) {
   7:     $timeout(function () {
   8:         return resolve(context);
   9:     }, 1000);
  10: });

Similarly, "tabs" can be a promise as well.

   1: $scope.tabs = $q(function (resolve, reject) {
   2:     $timeout(function () {
   3:         return resolve(tabs);
   4:     }, 1000);
   5: });

The directive is smart enough to handler promise case, or normal object case.



In this post I introduced an Angular.js directive I created and am using in my project, which is a wrapper of Bootstrap tabs with more customization features.

Different from the tabs implemented in UI-Bootstrap, you can define tab's template and controller separately. And it will be loaded, rendered when the tab was activated. You can also pass objects that can be shared through all tabs, but each tab scope are isolated. It supports customize entering and leaving function, tab show, hide and switch function. It also accept tab definition and shared data as object or promise.

You can find the online demo at this plunker. You can find the source code in GitHub. If you found any bugs or any feature requirements please feel free to open issues.


Hope this helps,


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.

I started to use Geekwithblogs (a.k.a. GWB) since 2010, based on one of my friend's recommendation. I've to say during the past 5+ years I was really enjoying blogging and had published 107 posts with 380 comments. GWB provided an awesome platform where I can share my experience and discuss with a lot of talents.


But since last month I found my blog look strange. On May 29th I found all my categories are lost. And when I tried to create a new category it still cannot be saved. This means all my well-categorized 107 posts are messed up.


Several days later I found my gallery was emptied in admin page, too, even though I can access images stored there.



Well I think this is not a big issue. Maybe GWB was updating, or maybe my site was hacked. So when I found the issue on May 29th I tried to contact Jeff Julian, the staff of GWB who helped me to map to my blog before. But no response till now.

Then I tried to find any channels to the team of GWB, but no luck. There seems no entry or link on, or in admin page mentions how to contact them. Finally I tried to use the "Suggest" link on and posted an item, but still no reply till now.



Today I suddenly found my blog theme was changed. After resumed the theme I think this might be the only way to report my problem, which is to publish a post. Sorry if I border you but I really want to check what's going on with GWB? Is there anyone who is still maintaining this site?


Hope anyone can help me,


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.

Today when I upgraded my application from Angular.js 1.3.9 to the latest 1.4, I got some bugs. After investigated a bit I found they are related with date properties and "angular.merge" function, which was included in 1.4.

In the official document, "angular.merge" is

Deeply extends the destination object "dst" by copying own enumerable properties from the "src" object(s) to "dst".

It also mentioned the different with "angular.extend", which had been included in previous versions.

Unlike "extend()", "merge()" recursively descends into object properties of source objects, performing a deep copy.


Let's have a look on a very simple example. In code below I have a source object and custom object defined in scope. And I used "angular.extend" to copy custom object into source.

   1: $scope.source = {
   2:   name: 'Shaun',
   3:   age: 35,
   4:   birthDate: new Date(1997, 5, 27),
   5:   skills: {
   6:     dotNet: {
   7:       level: 'expert',
   8:       years: 10
   9:     },
  10:     javaScript: {
  11:       level: 'newbie',
  12:       years: 2
  13:     }
  14:   },
  15:   mvp: [
  16:     2011,
  17:     2012,
  18:     2013,
  19:     2014,
  20:     2015
  21:   ]
  22: };
  24: $scope.custom = {
  25:   name: 'Ziyan',
  26:   age: 35,
  27:   skills: {
  28:     dotNet: {
  29:       level: 'hero', 
  30:       years: 100,
  31:       active: true
  32:     },
  33:   },
  34:   mvp: [
  35:     2016
  36:   ]
  37: };
  39: $scope.extend = angular.extend({}, $scope.source, $scope.custom);

From the result we can see, since "angular.extend" performs shallow copy, primitive value property such as "name", "age" and "bitrhDate" are merged. But since "skills" and "mvp" are object and array, "angular.extend" will just copy the entire value, rather than their members.



Now let's using "angular.merge".

   1: $scope.merge = angular.merge({}, $scope.source, $scope.custom);

Now we can see, when using "angular.merge", it will copy object properties recursively.



Everything looks great till now. But someone may find when using "angular.merge", one of the property, "birthDate" was been set as am empty object.


If we deep into the source code of Angular.js we will find, both "angular.extend" and "angular.merge" are invoking an internal function named "baseExtend". It merges objects to destination object, with a flag parameter indicates whether it's a deep merge or not.

Inside this function, it loops each enumerable properties, try to copy the value to destination object. If the source property is an object and need to be deeply copied, Angular.js will create an empty object in destination and perform this function against this property recursively.

   1: function baseExtend(dst, objs, deep) {
   2:   var h = dst.$$hashKey;
   4:   for (var i = 0, ii = objs.length; i < ii; ++i) {
   5:     var obj = objs[i];
   6:     if (!isObject(obj) && !isFunction(obj)) continue;
   7:     var keys = Object.keys(obj);
   8:     for (var j = 0, jj = keys.length; j < jj; j++) {
   9:       var key = keys[j];
  10:       var src = obj[key];
  12:       if (deep && isObject(src)) {
  13:         if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
  14:         baseExtend(dst[key], [src], true);
  15:       } else {
  16:         dst[key] = src;
  17:       }
  18:     }
  19:   }
  21:   setHashKey(dst, h);
  22:   return dst;
  23: }

It works in almost all cases but Date. If we have a Date property defined, for example "birthDate", it will check if this property is an object by using "angular.isObject" and it will return "true". So it will create a property named "birthDate" in destination with an empty object, and invoke "baseExtend" against "birthDate". But since "birthDate" is a Date which is no enumerable property, so it will not assign any data. This is the reason we found in result, "birthDate" property is empty.

If I copied Angular.js "baseExtend" function to my scope, and changed the code as below, which will perform simple copy when the property is Date, it will work.

   1: $scope.$baseExtend = function (dst, objs, deep) {
   2:   for (var i = 0, ii = objs.length; i < ii; ++i) {
   3:     var obj = objs[i];
   4:     if (!angular.isObject(obj) && !angular.isFunction(obj)) {
   5:       console.log('[' + obj + '] = (skip)');
   6:       continue;
   7:     }
   8:     var keys = Object.keys(obj);
   9:     for (var j = 0, jj = keys.length; j < jj; j++) {
  10:       var key = keys[j];
  11:       var src = obj[key];
  12:       // perform deep copy if
  13:       // 1. spcified by user
  14:       // 2. source property is an object
  15:       // 3. source property is NOT a date
  16:       if (deep && angular.isObject(src) && !angular.isDate(src)) {
  17:         if (!angular.isObject(dst[key])) {
  18:           console.log('[' + key + '] = (Try copy an object to an non-object, create an empty for deep copy.)');
  19:           dst[key] = angular.isArray(src) ? [] : {};
  20:         }
  21:         $scope.$baseExtend(dst[key], [src], true);
  22:       } 
  23:       else {
  24:         dst[key] = src;
  26:         console.log('[' + key + '] = ' + src);
  27:       }
  28:     }
  29:   }
  30:   return dst;
  31: };
  33: $scope.sample = $scope.$baseExtend({}, [$scope.source, $scope.custom], true);

This is the result.




Upgrade framework to a new version is always be an adventure. We need to perform a lot of regression tests to make sure it will not break anything. This problem was found when I perform E2E tests.

I think this is bug in Angular.js and I had posted an issue. But before their verification and response, I think you should pay more attention when using "angular.merge" function in your application.


Hope this helps,


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.

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>
   7:         <hr>
   8:     </div>
   9:   </div>
  11:   <script>
   2:     (function() {
   3:       var app = angular.module('Demo', []);
   5:       app.controller('HomeCtrl', function($scope) {
   6:         $scope.person = {
   7:           name: 'Shaun',
   8:           age: 36,
   9:           Company: 'IGT'
  10:         };
  11:       });
  12:     }());
  12: </body>

And below is what the page looks like.


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="">
   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="">
  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>
   6:       <hr>
   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.


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. (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>$ = {{foo}}</pre>
   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="">
   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="">
  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 "". 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. (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.


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="{{}} is {{person.age}} years old working at {{}}."></div>
Here is the result.

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="{{}} is {{person.age}} years old working at {{}}."
   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. (3)



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,


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.

When I'm working in my project, as well as development "sx.wizard" toolkit, I need to deal with asynchronous operation. For example, in "sx.wizard", template for each steps are loaded in parallel asynchronously. And the "$wizard" service will start to render UI once all templates are loaded successfully. In this case we need to use $q.


$q was explained in Angular.js document as follow.

A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.

And it's inspired by Kris Kowal's Q, which is widely used in Node.js. It's also following ES6 promises.

To me, when you have a function which the result will be returned in sometime in the future, and you need to continue your procedure based on the return value, then you need to consider using $q. Let's take a very simple example.


Handle Asynchronous Operation

Assuming I have an angular.js application which need to display "Hello Shaun!", and "Shaun" should be appeared 2 seconds later, the code could be as below.

   1: <!DOCTYPE html>
   2: <html>
   3:   <head>
   4:     <script data-require="angular.js@1.4.0-rc.1" data-semver="1.4.0-rc.1" src=""></script>
   2:   </head>
   4:   <body ng-app="Demo" ng-controller="HomeCtrl">
   5:     <h1>Hello {{name}}!</h1>
   7:       <script>
   8:         (function () {
   9:             var app = angular.module('Demo', []);
  11:             app.controller('HomeCtrl', function ($scope, $timeout) {
  12:                 $timeout(function () {
  13:                     $ = 'Shaun';
  14:                 }, 2000);
  15:             }); 
  16:         }());
   6:   </body>
   7: </html>

Here is the result.


If we want to continue our procedure we can simply add codes inside $timeout function as below.

   1: app.controller('HomeCtrl', function ($scope, $timeout) {
   2:     $timeout(function () {
   3:         $ = 'Shaun';
   4:         alert('Continue working...');
   5:     }, 2000);
   6: }); 

In this case it seems no need to introduce $q. But let's assume the name will be retrieved from another service or component. Normally it might be retrieved some external web service through $http but in order to simplify the example let create service and let it return name value in 2 seconds.

   1: app.factory('nameService', function ($timeout) {
   2:     return {
   3:         get: function () {
   4:             $timeout(function () {
   5:                 return 'Shaun';
   6:             }, 2000);
   7:         }
   8:     };
   9: });

In controller we use this service.

   1: app.controller('HomeCtrl', function ($scope, nameService) {
   2:     $ = nameService.get();
   3: }); 

Looks great. But when we launch our website we will find the name will never be shown in the page, even though in the service we returned in 2 seconds. This is because "nameService.get" function returned "undefined" after it processed $timeout. 2 seconds later we returned name value but our controller has no chance to get it.



In this case we can use $q to notify controller the name was retrieved please update scope.

   1: app.factory('nameService', function ($q, $timeout) {
   2:     return {
   3:         get: function () {
   4:             return $q(function (resolve, reject) {
   5:                 $timeout(function () {
   6:                     resolve('Shaun');
   7:                 }, 2000);
   8:             });
   9:         }
  10:     };
  11: });

We use "$q" as a constructor, which takes a function with two parameters: resolve and reject, both of them are functions. When we got the value, we can invoke "resolve" with the value as the parameter. When there's an exception, we can invoke "reject" to tell the caller it failed. In the code above I invoked "resolve" after 2 seconds with "Shaun", the name value I got.

Back to the controller, after invoked "nameService.get" we got a promise created by $q. When the result was ready it will invoke a function defined through its "then" method as following.

   1: app.controller('HomeCtrl', function ($scope, nameService) {
   2:     var getPromise = nameService.get();
   4:     getPromise.then(function (name) {
   5:         $ = name;
   6:     });
   7: }); 


Invocation Chaining

We can return a new promise inside "then" function from an existing promise. This makes us very easy to manage multiple asynchronous functions especially when some are depends on others. Database operation is a good example. Normally we need to open the database, then it's opened we will query records, and then we got the records we will filter them, prepare data and insert or update, then we close the connection. Below is "database" service I created, note that each function returns a promise.

   1: app.factory('database', function ($q, $timeout) {
   2:     return {
   3:         open: function (db) {
   4:             return $q(function (resolve, reject) {
   5:                 $timeout(function () {
   6:                     resolve(db + ' is connected.');
   7:                 }, 2000);
   8:             });
   9:         },
  10:         query: function (db, query) {
  11:             return $q(function (resolve, reject) {
  12:                 $timeout(function () {
  13:                     resolve(db + ' [' + query + '] processed successuflly.');
  14:                 }, 2000);
  15:             });  
  16:         },
  17:         execute: function (db, command) {
  18:             return $q(function (resolve, reject) {
  19:                 $timeout(function () {
  20:                     resolve(db + ' [' + command + '] invoked successuflly.');
  21:                 }, 2000);
  22:             });  
  23:         },
  24:         close: function (db) {
  25:             return $q(function (resolve, reject) {
  26:                 $timeout(function () {
  27:                     resolve(db + ' is disconnected.');
  28:                 }, 2000);
  29:             });  
  30:         }
  31:     };
  32: });

In controller I invoked "" method and handle and append a log entry when connected. Note that in this function I invoked "database.query", which return another promise.

   1: app.controller('HomeCtrl', function ($scope, database) {
   2:     $scope.logs = [];
   3:     var db = 'shaun-db';
   6:             .then(function (result) {
   7:                 $scope.logs.push(result);
   8:                 return database.query(db, 'SELECT * FROM [Products]');
   9:             });
  10: }); 

Now I can invoke "then" right after it, which called "invocation chaining", to perform my next operation.

   1: app.controller('HomeCtrl', function ($scope, database) {
   2:     $scope.logs = [];
   3:     var db = 'shaun-db';
   6:             .then(function (result) {
   7:                 $scope.logs.push(result);
   8:                 return database.query(db, 'SELECT * FROM [Products]');
   9:             })
  10:             .then(function (result) {
  11:                 $scope.logs.push(result);
  12:                 return database.execute(db, 'INSERT INTO [Products] VALUE (5, \'Worktile\')');
  13:             })
  14:             .then(function (result) {
  15:                 $scope.logs.push(result);
  16:                 return database.close(db);
  17:             })
  18:             .then(function (result) {
  19:                 $scope.logs.push(result);
  20:             });
  21: }); 

Now let's run it you will see the operations are invoked one by one in series.



$q also supports execute asynchronous functions in parallel. In the example above we invoked one command when database was connected. It's a common case we can run some query in parallel, and when all of them are finished we will run some commands, then close the connection.

Below I tweaked "database" service a little bit in its "execute" method, allow user specify the timeout period.

   1: execute: function (db, command, timeout) {
   2:     return $q(function (resolve, reject) {
   3:         $timeout(function () {
   4:             resolve(db + ' [' + command + '] invoked successuflly.');
   5:         }, timeout); 
   6:     });  
   7: },

In controller when the database was connected and first query finished, I invoked 5 commands through "execute" method, which will be processed in parallel, and pushed the promises into an array, Then I was using "$q.all" method. This method accepts an array of promises and will invoke its "then" when all of them are resolved.

   1: .then(function (result) {
   2:     $scope.logs.push(result);
   3:     var commandPromises = [];
   4:     commandPromises.push(database.execute(db, 'CMD 1', 1000));
   5:     commandPromises.push(database.execute(db, 'CMD 2', 500));
   6:     commandPromises.push(database.execute(db, 'CMD 3', 200));
   7:     commandPromises.push(database.execute(db, 'CMD 4', 200));
   8:     commandPromises.push(database.execute(db, 'CMD 5', 3000));
   9:     return $q.all(commandPromises);
  10: })

After that I executed two commands in parallel in the same way, and close the database connection when all of them are finished.

   1: .then(function (result) {
   2:     $scope.logs.push(result);
   3:     var commandPromises = [];
   4:     commandPromises.push(database.execute(db, 'CMD 6', 200));
   5:     commandPromises.push(database.execute(db, 'CMD 7', 300));
   6:     return $q.all(commandPromises);
   7: })

When we run our application you will see the procedure was waiting for all parallel commands finished then process the next one.



Examples in Shaun's Angular Toolkits

Now let's take a look on how I used $q in the code of my angular toolkits. In "sx.wizard" user can specify step template URL or template inline code. When "template" property was specified I will use it directly, when "templateUrl" was specified I will load the content through $http, which is an asynchronous operation. Hence I have a function responsible for creating template promise.

   1: var _getTemplatePromise = function(step) {
   2:     if (step.template) {
   3:         step.template = '' +
   4:             '<div class="sx-wizard-step" sx-wizard-step-id="' + + '">' +
   5:             step.template +
   6:             '</div>';
   7:         return $q.when(step);
   8:     } else {
   9:         return $http.get(step.templateUrl).then(function(response) {
  10:             step.template = '' +
  11:                 '<div class="sx-wizard-step" sx-wizard-step-id="' + + '">' +
  12:        +
  13:                 '</div>';
  14:             return step;
  15:         });
  16:     }
  17: };

In the code above I checked if "template" property was specified. If so I will wrap some HTML code and return a new promise through "$q.when". "$q.when" can be used when we are not pretty sure if an object is a normal object or a promise. It create a new promise wrapping this kind of object so that we can dealing it in $q. If the value passed in "$q.when" is a normal object it will be resolved immediately.

If "templateUrl" was specified I invoked "$http.get" to retrieve template content. Since "$http.get" returns a promise I use its "then" method to assign the content to step's template property.

Finally I returned the promise, no matter created by "$q.when" or "$http.get", into a local array.

   1: var _stepTemplatePromises = [];
   3: ... ...
   5: wizard.addStep = function(step) {
   6:     ... ...
   7:     _stepTemplatePromises.push(_getTemplatePromise(step));
   8:     return wizard;
   9: };

Then when the wizard is going to be shown, it will wait until all template are loaded successfully through "$q.all", then start to prepare scope and render UI.

   1: = function(data, success, cancel) {
   2:     ... ...
   3:     $q.all(_stepTemplatePromises).then(function() {
   4:         var instance = ${
   5:             ... ...
   6:         });
   7:         instance.result.then(function(data) {
   8:             return success(data);
   9:         }, function() {
  10:             return cancel();
  11:         });
  12:     });
  13:     return wizard;
  14: };


Another example in "sx.tabs", which I'm going to introduce in the near future. User can specify tab content through inline template or URL. So I have the similar function for tab content loading.

   1: var _setTemplatePromise = function (tab) {
   2:     if (tab.template) {
   3:         tab.$templatePromise = $q.when(tab);
   4:     }
   5:     else {
   6:         tab.$templatePromise = $http.get(tab.templateUrl, {cache: $templateCache}).then(function (response) {
   7:             tab.template =;
   8:             return tab;
   9:         });
  10:     }
  11: };

Then a tab was clicked, I checked the template promise status, when it's resolved I will start execute the tab's controller, compile and render.

   1: scope.switchTab = function (options, callback) {
   2:     ... ...
   3:     if (id && (!scope.activeTab || ! || !== id)) {
   4:         var tab = scope.$tabs[id];
   5:         if (tab) {
   6:             $q.when(tab.$templatePromise).then(function () { 
   7:                 ... ...
   8:             });
   9:         }
  10:         else {
  11:             return callback(false);
  12:         }
  13:     }
  14:     else {
  15:         return callback(false);
  16:     }
  17: };



Asynchronous function is very common in JavaScript. There are many libraries handles this scenario. Angular.js support it by using "$q". Comparing with others pattern "$q" is lightweight and simple, with enough functionalities. And all asynchronous components in Angular.js are built based on "$q" such as "$http", "$timeout", etc.

When your operation is asynchronous, and you need to invoke some following operations after it finished, you should think about "$q" firstly.


Hope this helps,


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.