Tuesday 22 December 2015

Event based communication between controllers.


Every angular application has a single root Scope ($rootScope). All the scopes are its descendants.

Scopes can generate an  events by emitting ($emit) or broadcasting ($broadcast). Apart from generating events, scope can listen to events as well. Its is achieved by using the $scope.$on 

$emit, $broadcast and $on follow the standard "publish / subscribe" design pattern.


Understanding the $scope methods for event based communication between angular controllers

  • $scope.$emit:  This method dispatches the event in the upwards direction (from child to  parent)
  • $scope.$broadcast: Method dispatches the event in the downwards direction (from parent to child)  to all the child controllers.
  • $scope.$on: Method registers to listen to some event. All the controllers which are listening to that event gets notification of the broadcast or emit based on the where those fit in the child parent hierarchy.  

If we consider a case where "grand_child" controller calls the $emits method on its scope. The same "grand_child" controller has also listening to the same event which is emitted by itself. In this situation when "grand_child" calls $emit, the same controller gets its own notification. 

Essentially all the controllers in the hierarchy gets notification for $emit (in upwards direction).
Note : In case of sibling scopes (the scopes which are not in the direct parent child hierarchy) then $emit and $broadcast will not communicate to the sibling scopes.



$rootScope.$broadcast:  $rootScope is the parent of all the scopes. $rootScope.$broadcast will notify the event to all the controllers listening to $rootScope.$on as well as $scope.on



$rootScope.$emit :  We may think that calling $emit method is meaning-less as rootScope do not have any parent. But actually $rootScope.$emit fires an event to all the controllers listening $rootScope.$on event.  The controllers which are listening on $scope.$on have no effect of this emit.




Stopping the event being propagating 

The $emit event can be cancelled by any one of the $scope who is listening to the event.
The $on provides the "stopPropagation" method. By calling this method the event can be stopped from propagating further.

This method is available only for the events which are emitted($emit). The same is not available for $broadcast.

The events which are broadcast can't be stopped. Although we can registered the scope from listening the event. 
$on function returns a function.
.controller('T1Parent',['$scope',function($scope){
   $scope.T1EventVar = '';
   $scope.stopPropagation=false;
   $scope.stopBroadcast = false;
   var fn = $scope.$on('Tree1Event',function(e,arg){
	$scope.T1EventVar = arg;
	if($scope.stopPropagation) {
		e.stopPropagation();  
	}
	if($scope.stopBroadcast){
		fn();  // This will cancel the listening of the event 'Tree1Event'
	}
   });
   $scope.broadCastEvent=function(){
	 $scope.$broadcast('Tree1Event','T1 Parent Brodacated');
   };
   $scope.emitEvent=function(){
	 $scope.$emit('Tree1Event','T1 Parent Emitted');
   };
}])

The complete example can be see on the following plunk. This example demonstrates the emit, broadcast, rootScope.$emit, rootScope.$broadcast, stopping the propagation and cancelling the listening of the event from a particular controller's scope.

Demo http://plnkr.co/edit/0Pdrrtj3GEnMp2UpILp4?p=preview


source code emit-vs-broadcast.js


angular.module('dlgDemo',[])
.controller('GrandParent1',['$scope','$rootScope',function($scope,$rootScope){
   $scope.GrandParent1Var = '';
   //Listening the Tree 1 Child Event
   $scope.$on('T1childEmitEvent',function(e,arg){
	console.log("Hello");
	$scope.T1EventVar = arg;
   });
	// Listening to Tree 2 Grand Parent Event
   $scope.$on('Tree1Event',function(e,arg){
	$scope.T1EventVar = arg;
   });
   $scope.broadCastEvent=function(){
	   console.log("broadCastEvent");
	 $scope.$broadcast('Tree1Event','T1 Grand Parent Brodacated');
   };
   $scope.emitEvent=function(){
	 $scope.$emit('Tree1Event','T1 Grand Parent Emitted');
   };
   $scope.rootBroadcast = function() {
	   $rootScope.$broadcast('Tree1Event','$rootScope.$broadcast');
   };
   $scope.rootEmit = function() {
	   $rootScope.$emit('Tree1Event','$rootScope.$emit');
   };
}])
.controller('T1Parent',['$scope',function($scope){
   $scope.T1EventVar = '';
   $scope.stopPropagation=false;
   var fn = $scope.$on('Tree1Event',function(e,arg){
	   console.log(e);
	$scope.T1EventVar = arg;
	if($scope.stopPropagation) {
		e.stopPropagation();
	}
	if($scope.stopBroadcast){
		fn();
	}
   });
   $scope.broadCastEvent=function(){
	 $scope.$broadcast('Tree1Event','T1 Parent Brodacated');
   };
   $scope.emitEvent=function(){
	 $scope.$emit('Tree1Event','T1 Parent Emitted');
   };
}])

.controller('T1Child',['$scope','$rootScope',function($scope,$rootScope){
	$scope.T1EventVar = '';
	$scope.T1EventVarFromRoot = '';
   $scope.$on('Tree1Event',function(e,arg){
	console.log(e);
	$scope.T1EventVar = arg;
   });
   
   $scope.emitEvent = function() {
	$scope.$emit('Tree1Event','T1 Child Emitted');
   };

   $scope.broadCastEvent=function(){
	 //$scope.$broadcast('Tree1Event','T1 Child Brodacated');
	 $rootScope.$broadcast('Tree1Event','$rootScope.$broadcast');
   };
   $rootScope.$on('Tree1Event',  function(e,arg){
	   $scope.T1EventVar = arg;
   });
   
}])

.controller('GrandParent2',['$scope',function($scope){
   $scope.T1EventVar = '';
   //Listening the Tree 1 Child Event
   $scope.$on('Tree1Event',function(e,arg){
	$scope.T1EventVar = arg;
   });

   $scope.broadCastEvent=function(){
	 $scope.$broadcast('Tree1Event','T2 Grand Parent Brodacated');
   };

   $scope.emitEvent=function(){
	 $scope.$emit('Tree1Event','T2 Grand Parent Emitted');
   };
}])
.controller('T2Parent',['$scope',function($scope){
   $scope.T1EventVar = '';
   $scope.$on('Tree1Event',function(e,arg){
	$scope.T1EventVar = arg;
   });
   
   $scope.emitEvent = function() {
	$scope.$emit('Tree1Event','T2 Parent Emitted');
   };

   $scope.broadCastEvent=function(){
	 $scope.$broadcast('Tree1Event','T2 Parent Brodacated');
   };
}])
.controller('T2Child',['$scope',function($scope){
	$scope.T1EventVar = '';
   $scope.$on('Tree1Event',function(e,arg){
	$scope.T1EventVar = arg;
   });
   $scope.emitEvent = function() {
	$scope.$emit('Tree1Event','T2 Child Emitted');
   };

   $scope.broadCastEvent=function(){
	 $scope.$broadcast('Tree1Event','T2 Child Brodacated');
   };
   
}]);



Source HTML:



Tree 1 : Grand parant

Event Event Listener1
{{T1EventVar}}

Tree 1 : Parent

Stop Propagation Emit event
Deregister the scope from listening event
Event Event Listener1
{{T1EventVar}}

Tree 1 : Grand Child

Event Event Listener1
{{T1EventVar}}

Tree 2 : Grand parant

Event Event Listener1
{{T1EventVar}}

Tree 2 : Parent

Event Event Listener1
{{T1EventVar}}

Tree 2 : Grand Child

Event Event Listener1
{{T1EventVar}}


How to debug minified JavaScript files


We can debug JavaScript files by placing the debug / break points in the dev tools. Generally the JavaScript files on the production are minified.

To debug such minified files, Web browsers provides the "Pretty print" feature which un-minifies the source file.
  • How to un-minify JavaScript file in Chrome
     Open the developer tools (F12) > Source tab > Open the minified script that you want debug  (ctrl + o)  > at the bottom you will see "Pretty print" button ({})




  • How to un-minify JavaScript file in Firefox

      Open the developer tools (F12) > go to Debugger tab > Open the minified that you want debug (ctrl + o) > at the bottom you will see "Prettify source" button ({})


  • How to un-minify JavaScript file in Internet Explorer
      Open the developer tools (F12) > go to Debugger tab > Open the minified that you want debug  (ctrl + o) > at the top icon panel you will see  a button with tooltip (Load the sources mapped to this generated file) to un-minify the JS file



Thursday 17 December 2015


Angular Modal Dialog





ngDialog is the angular directive used for displaying the modal windows in the angular application.



Features
  • With this directive we can create the custom dialog box easily. We can create the dialog with 
    1. Bootstrap theme
    2. Standard theme which is available with directive. The CSS classes can be customized as per your need
    3. and the custom user defined templates
  • We can  put as many buttons as we need on the dialog to take the necessary actions on the button clicks.
    1. These button functions should be defined in the controller that you have assigned to the view. 
    2. e.g if we want to show the two button on the dialog say "Ok" and "Cancel" then we can write the markup as  button="Ok|okFunction,Cancel|cancelFunction 
      • Here the Ok => will create the button with Label "Ok" on the click event of this "Ok button" the okFunction defined in the controller will be called. Similarly another button "Cancel" will be shown on the dialog which is bound to the scope function cancelFunction
Attribute configuration in the directive markup
  1. dialogid : HTML Id of the dialog created
  2. header   : Header text that should appear on the modal dialog
  3. Message : Message text of the modal dialog
  4. button    : buttons those should appear in the modal dialog,  The number of buttons should be ", (comma)" seperated. For each button the function that should get invoked should be contacted with the "| (Pipe)" Operator
    1. e.g  button="OkButton|okFunction,CancelButton|cancelFunction"
Here is the markup 

Bootstrap Theme

you need to included the following JS in your code












     


Standard Theme









     


Custom Theme


     

HTML code





1. 
    
     
     


2.  
     
     



3. 

          
     







Directive Code
(function(){
 'use strict';
 angular.module('ngDialog',[]);
})();
(function(){
 'use strict';
 angular.module('ngDialog').directive('ngDialog',
  directiveFunc);
 directiveFunc.$inject = [ '$templateCache','$filter'];
 
 function directiveFunc($templateCache,$filter) {
  var dialogBoxConfig = {
   theme: 'bootstrap',
  };
  return {
   restrict : 'AE',
   transclude: true,
   scope : {
    id :"@dialogid",
    header:"@header",
    message:"@message",
    button: "@button",
    theme : "@theme"
   },
   link : function(scope,element,attr) { 
    var buttonAttributeText = attr.button;
    if(buttonAttributeText!= undefined && buttonAttributeText!=''){ 
     var buttonList = null;
     var buttonListJSON = [];
     if(buttonAttributeText.indexOf(",") > 0) {
      buttonList = buttonAttributeText.split(",")
     }else {
      buttonList = createArrayFromObject(buttonAttributeText);
     }
     angular.forEach(buttonList,function(buttonDetails,index){
      var buttonAttributes = null;
      if(buttonDetails.indexOf("|") > 0 ){
       buttonAttributes = buttonDetails.split("|");
      }else {
       //debugger;
       buttonAttributes = createArrayFromObject(buttonDetails.trim()); 
      }      
      buttonListJSON.push({'buttonLabel':buttonAttributes[0],'buttonClickEventFunction':buttonAttributes[1]});
      scope.buttonListJSON = buttonListJSON;
     });
    }else {
     throw "No buttons provided. e.g  ";     
    }
    var messageAttributeText = attr.message;
    if(messageAttributeText!=undefined && messageAttributeText!=''){ 
     var messageList = null;
     var messageListJSON = [];
     if(messageAttributeText.indexOf(",") > 0) {
      messageList = messageAttributeText.split(",")
     }else {
      messageList = createArrayFromObject(messageAttributeText);
     }
     angular.forEach(messageList,function(buttonDetails,index){
      
      var messageAttributes = createArrayFromObject(buttonDetails.trim()); 
      messageListJSON.push({'messageLabel':messageAttributes[0]});
      scope.messageListJSON = messageListJSON;
     });
    }else {
     throw "No dialog message body provided. e.g  ";
    }
   },
   //templateUrl : config.modalsDir+'ngDialog.html',
   //templateUrl: 'tmpls/'+theme+'.html',
    templateUrl: function(tElement, tAttrs) {
     var _theme  = '';
     if(tAttrs.theme === undefined ){
      _theme = 'BOOTSTRAP';
     }else {
      _theme = $filter('uppercase')(tAttrs.theme);
     }      
       if (_theme === 'STANDARD') {  return 'tmpls/standard.html';}
       if (_theme === 'CUSTOM') {  return 'tmpls/'+tAttrs.theme+'.html';}
       if (_theme === 'BOOTSTRAP') {  return 'tmpls/bootstrap.html' };
   },
   controller : function($scope) {
    if($scope.theme === undefined){
     $scope.theme = 'bootstrap';
    }
    $scope.hasValue = function(val){
     var value = $scope.$parent[val.trim()];
     return (value !== null && angular.isDefined(value) && value !== '');
    }
    $scope.myclose = function(arg){
     $("#" + $scope.id).css({
      display : "none"
     });
     if(angular.isDefined(arg) && $scope.hasValue(arg)){
      var funcCall = "$scope.$parent."+arg+"()"; 
      $()
      var retValue = eval(funcCall);
     }
    }
   },
  };
 }
})();

angular.module('ngDialog')
 .run(['$templateCache',function($templateCache){
  $templateCache.put('tmpls/standard.html',
   '
' +'
' + '
' + '

{{ header}}

' + '
' + '
{{messageAttributes.messageLabel}}
' + '
' + '
' + '' + '
' + '
' + '
' + '
'); $templateCache.put('tmpls/bootstrap.html', ' '); }]); function createArrayFromObject(object) { var result = []; if (angular.isArray(object)) { //it is array result = object; }else if (object) { result.push(object); } return result; }
CSS :
.ng-dlg-bg > div {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}
.ng-dlg {
    text-align: left;
    background: #eaeced;
    width: 350px;
    box-shadow: 0 7px 15px #80898f;
    border-radius: 2px;
    margin: 0 auto;
    display: table;
    position: relative;
}
.ng-dlg > h3 {
    font-weight: normal;
    padding: 15px 20px 10px 20px;
    border-radius: 2px 2px 0 0;
    background: #fff;
    border-bottom: 1px solid #dadcdd;
    font-size: 18px;
    color: #676a6f;
    margin: 0;
}
.ng-dlg > p {
    text-align: right;
    padding: 15px 20px;
    margin: 0;
}
.ng-dlg > p > button {
    width: auto;
    padding: 5px 25px;
    min-width: 80px;
    margin: 0 0 0 5px;
}
.ng-dlg > div {
    margin: 15px 20px 0 20px;
}
.ng-dlg > div > p {
    margin: 7px 0 0 0;
    padding: 0;
}

.ng-dlg-bg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: none;
    z-index: 500;
}
.ng-glass {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: #73808c;
    z-index: 499;
    display: none;
}

Licence : MIT

Monday 7 December 2015


Lifecycle of AngularJS directive. 

AngularJS comes with rich set of in built directives which adds a specific behavior to the HTML markes by transforming the DOM elements.
Some examples of directives are ngBind, ngModel.

AngularJS provides a way to create custom directives as well.

In this tutorial we will understand the directive life cycle and how we can create the custom directive in the consecutive one.

In case of ng-app directive, AngularJS is initialized automatically as soon as HTML document is loaded. AngularJS looks for the directive "ng-app" in the HTML elements. This element then becomes the root of the angular application. "ng-app" directive auto initializes the angular application. This process is called "auto bootstrapping" although we can manually bootstrap angular application.

During the bootstrap process, AngularJS
1. Loads the angular module mentioned within the ng-app directive
2. Creates $injector service responsible for DI in the application.
3. Runs the $compile service.

Typical flow diagram having three directive


1.The compilation Phase:

$compile service looks at the text and attributes of the DOM element. Identifies the directives and embedded expressions. Registers watch for these expressions and updates the UI during angular digest cycle. The compile service compiles the DOM from root element and traverse down the DOM by using depth first traversal algorithm.
During this phase DOM element and its attributes are identified as directive. The directive names are normalized using camleCase. e.g.

  • 1. ng-app is normalized as ngApp,
  • 2. my-custom-directive is normalized as myCustomDirective
  • 3. x-my-custom-directive is normalized as myCustomDirective
  • 4. data-my-custom-directive is normalized as myCustomDirective
  • 5. x: my: custom: directive is normalized as myCustomDirective
  • 6. data:my:custom:directive is normalized as myCustomDirective


':', '-' characters are converted to the camelCase
'data', 'x' are removed from the element and attributes.

Linking phase:

2.  Controller functions: 
After compilation controller and followed by pre-linking function is executed respectively for each directive.
controller facilitates the communication between child and parent directive.  The significance of the controller inside the directive is to
1. modify the $scope of the template and
2 if requested by the child controller, this controller function / object is passed to the child controller and both the controllers can communicate with each other

3. Pre link functions :
After this pre-link function is called. This function can be used manipulate the directive template by appending or prepending.  The pre link function always has the private context with its own directive and can't be accessed by the child controllers.

4. . Post link function ;
Finally post-link function is called for each directive. The order of calling this function is exactly opposite to the compile function. Its starts from the last node and moves up till the root element. In most of the cases we can use this function to apply the CSS to the directive templates.

Typical directive example

customDirective.js having compile function
 

 var app = angular.module("demo",[]);
 app.controller("demoCntrl", function($scope,$q,utilService){
  console.log("inside main controller");
  $scope.tableOptions = {
   headerTemplate: true,
   footerTemplatle: true,
   data : [
    {name:'John'},
    {name:'Varghese'},
    {name:'Mike'}
   ]
  }
 });
 app.directive("myCustomTable", ['$compile','utilService', function($compile,utilService){
   return {
  scope :{
   model:"=",
  },
  transclude : false,
  templateUrl : 'tmpl/tablerow.html',
  compile: function($element, $attributes){
   return {
    pre : function($scope, $element, $attributes, controller, transcludeFn) {
     if($scope.model.headerTemplate !== undefined && $scope.model.headerTemplate === true){
     var template = utilService.getTemplate('tmpl/tableHeader.html');
      var x = $compile(template)($scope);
      $element.prepend(x);
     }
     if($scope.model.footerTemplatle !== undefined && $scope.model.footerTemplatle === true){
      var tmpl = utilService.getTemplate('tmpl/tablefooter.html');
      console.log(tmpl);
      var x = $compile(tmpl)($scope);
      $element.append(x);
     }

    },
    post : function($scope, $element, $attributes, controller, transcludeFn){
     
     $element.addClass(".myCustomTable");
    }
   }
  },
  controller : function($scope,$templateCache,$q,$interpolate){
   if($scope.model.footerTemplatle == undefined ){
    $scope.model.footerTemplatle = false;
   }    
  },
   };
 }])
 .run(['$templateCache',function($templateCache){
 $templateCache.put('tmpl/tablerow.html','
{{record.name}}
'); $templateCache.put('tmpl/tablefooter.html', "showing {{model.data.length}} items" ); $templateCache.put('tmpl/tableHeader.html','Name
'); }]); app.factory('utilService',[ '$templateCache','$interpolate','$q',function($templateCache,$interpolate,$q){ var utilService = {}; utilService.getTemplate = function(templatePath){ var template = $templateCache.get(templatePath); var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol(); if (startSym !== '{{' || endSym !== '}}') { template = template.replace(/\{\{/g, startSym); template = template.replace(/\}\}/g, endSym); } return template; }; return utilService; }]);