Thursday, May 21, 2015

AngularJS Directive - Inherited Scope

Here's an example of a directive using inherited scope in AngularJS. See the plunk here:
http://plnkr.co/S8civ0l5KRL5cN2PBBJF

//index.html
<!DOCTYPE html>
<html ng-app="app">
  <head>
    <script data-require="jquery@2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js">           </script>
    <link data-require="bootstrap@*" data-semver="3.3.2" rel="stylesheet"     href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
    <script data-require="bootstrap@*" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <script data-require="angular.js@1.3.0" data-semver="1.3.0" src="https://code.angularjs.org/1.3.0/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>
  <body ng-controller="mainCtrl" class="container">
    <h5>
      <user-info-card user="user1"></user-info-card>
      <user-info-card user="user2"></user-info-card>
    </h5>
  </body>
</html>

/***************************************************************/
// script.js
angular.module('app',[]);
angular.module('app').controller('mainCtrl', function($scope){
  $scope.user1 = {
    name: "Luke Skywalker",
    address: {
      street: '6332 Cameron Forest Ln',
      city: "Charlotte",
      planet: "Earth"
    },
    friends: ['Han', 'Leia', 'Chewbacca']
  };

    $scope.user2 = {
    name: "Mia Farrow",
    address: {
      street: '1236 Cameron Forest Ln',
      city: "Charlotte",
      planet: "Vulcan"
    },
    friends: ['Lan', 'Tia', 'Chewbacca']
  };

  //console.log('parent scope', $scope);
  //Take this in the directive, so we don't break encapsulation and the directive handles this event
  /*
  $scope.knightMe = function(user){
    user.rank = "knight";
  }
  */
});
angular.module('app').directive('userInfoCard', function(){
  return {
    restrict: "E",
    templateUrl : "userInfoCard.html",
    replace: true, // will replace directive with the contents and not display the directive tag in the html element as best practice
    //scope: true, //inherited scope; false = shared scope with parent containing controller is the default
                 //scope: {}, would be isolated scope and the directive cannot see the controller's scope now
    scope: {
        user :"="
      }, //isolated scope but now controller(parent) scope user object can be shared by passing in
         // an user object user1 from the controller. Useful as the same directive can now be used across multiple controllers
         // and they can pass in thier scope user objects.
    controller: function($scope){
        $scope.collapsed = false; //parent variable
        $scope.knightMe = function(user){
              user.rank = "Knight";
         };
        $scope.collapse = function(){
          $scope.collapsed = !$scope.collapsed;
        }
    }
  }
});
//Put Address in its own directive so we can collapse it inside the panel-body of the useInfoCard directive.
//This will demonstrate inherited scope
angular.module('app').directive('address', function(){
  return {
    restrict: "E",
    scope: true,//THIS IS THE MAGIC ALLOWING Address to define its own collapsed variable, otherwise                     the variable is shared by default and clicking the address would close the entire panel-body
 
    templateUrl: "address.html",
    controller: function($scope){
   
      /* This statement below assigns another collapsed variable to address because of JavaScript's               prototypical inheritance.
         Otherwise, without this assignment statement that effectively creates(overrides) the collapsed variable for Address,
         it has the collapsed variable from the parent scope (userInfoCard directive in the html as address is nested inside that directive).
         Scope inheritance (setting scope: true in the child directive as above) thus propagates the scope from parent to child nested directive.
         In short, inheritance scope is very powerful as child directive can see properties in the parent scope, but be careful with it.
      */
      $scope.collapsed = false;
   
      $scope.collapseAddress = function(){
        $scope.collapsed = true;
      }
   
      $scope.expandAddress = function(){
        $scope.collapsed = false;
      }
    }
  }
})
                 // Note that we cannot make this isolated scope here because then user object in parent                                directive won't be accessible in the child directive

/*************************************************************************/

//userInfoCard.html parent directive
<div class="panel panel-primary">
  <!-- Have to wrap all elements within a root element for replace:true to work in  the directive -->
  <!-- The canonical Component directive. Almost always implemented as an element, defines a new widget-->
      <div class="panel-heading" ng-click="collapse()">{{user.name}} </div>
      <div class="panel-body" ng-hide="collapsed">
         <!--
         <h4>Address:</h4>
         <div ng-show='!!user.address' >
           {{user.address.street}}<br />
           City: {{user.address.city}}
         </div><br/>
        -->
        <!--Address is now in its own directive with inherited scope from userInfoCard directive-->
        <address></address>
     
        <h4>Friends:</h4>
        <ul>
          <li ng-repeat='friend in user.friends'>
            {{friend}}
          </li>
        </ul>
        <div ng-show="!!user.rank">
          <h4>Rank: {{user.rank}}</h4>
        </div><br/>
        <button class="btn-success" ng-click="knightMe(user)" ng-show="!user.rank">Knight Me</button>
      </div>
</div>


/**********************************************************************/

//address.html child directive that inherits scope

<div ng-show="!!user.address && !collapsed" ng-click="collapseAddress()">
   <h4>Address:</h4>
   <div ng-show='!!user.address' >
     {{user.address.street}}<br />
     City: {{user.address.city}}
   </div>
 </div>
 <div ng-show="!!user.address && collapsed" ng-click="expandAddress()">
   <h4>Address:</h4>
     {{user.address.street}}...
 </div>


No comments:

Post a Comment