25 days of AngularJS Calendar | 2013

The short, juicy guide of AngularJS information, designed to get you up and running without the fluff.

Go back | Day 22

A Few of My Favorite Things: Isolated Expression Scope

What do we do when we need to create a component that needs to be reusable and want to give other developers the ability to use that component however they wish?

In AngularJS, isolated expression scope is a really powerful tool that allows us to delegate units of work to the parent controller of a directive instead of handling it internally. This flexibility allows us to use the same directive over and over but have it do totally different things depending on how we define it in the HTML.

In this article, we are going to create a single directive that we will use seven times to do completely different things. When we are finished, we will have seen the power and flexibility of isolated expression scope applied in an approachable manner that you can start to using immediately. Let's get started!

The code for this article is here

Isolated Expression Scope

We are going to create a vote directive that has an vote-up and vote-down method. Isolated expression scope comes into play the moment we decide that we want to be able to increment and decrement the vote count by varying denominations. In our case, we want one vote directive instance to increment and decrement the vote count by 100, another to increment and decrement the vote count by 10 and so on.

We will use the HTML as a starting point and walk through how the vote directive actually comes to together. We have a MainController that has a property of votes that we are going to use to keep track of our total vote count. We also have three vote components on the page and in each one we are defining label, vote-up and vote-down differently.

<div class="container" ng-controller="MainController">
  <h1>Current Votes: {{votes}}</h1>
  <hr>
  <h3>Isolated Expression Scope</h3>
  <div class="vote-container">
    <vote label="100" vote-up="incrementHundred()" 
      vote-down="decrementHundred()"></vote>
    <vote label="10" vote-up="incrementTen()" 
      vote-down="decrementTen()"></vote>
    <vote label="1" vote-up="incrementOne()" 
      vote-down="decrementOne()"></vote>
  </div>
</div>

Notice that the label corresponds with the denomination that the vote directive increments or decrements the vote count. The directive with the label 100 has a method of incrementHundred and decrementHundred which does exactly that to the votes property.

Now that we have created some AngularJS requirements by defining them in our HTML, let us fulfill those in our JavaScript. First up, we have created the MainController with the votes property defined on scope as well as all of the increment and decrement properties we declared.

Just below that we are going to declare the vote directive.

angular.module('website', [])
  .controller('MainController', ['$scope',
    function($scope) {
      $scope.votes = 0;

      $scope.incrementOne = function() {
        $scope.votes += 1;
      };

      $scope.decrementOne = function() {
        $scope.votes -= 1;
      };

      $scope.incrementTen = function() {
        $scope.votes += 10;
      };

      $scope.decrementTen = function() {
        $scope.votes -= 10;
      };

      $scope.incrementHundred = function() {
        $scope.votes += 100;
      };

      $scope.decrementHundred = function() {
        $scope.votes -= 100;
      };
    }
  ])
  .directive('vote', function() {
    return {
      restrict: 'E',
      templateUrl: 'vote.html',
      scope: {
        voteUp: '&',
        voteDown: '&',
        label: '@'
      }
    };
  });

The vote directive is fairly simple in that we have restricted it to an element node with restrict: 'E' and declared the template with templateUrl: 'Vote.html'.

We are creating isolated scope by defining an object i.e.: {} for the scope property on the directive definition object.

An attribute isolated scope is being created for the label property with label: '@' which means we are creating a uni-directional binding from the parent scope to the directive to tell us what the label attribute evaluates to. This is appropriate because we only want to read the property and we never need to set it.

Expression isolated scope is being created for voteUp and voteDown with voteUp: '&' and voteDown: '&'. What this means is that when we call voteUp or voteDown in our directive, it will call whatever method we defined in that attribute on our directive.

For instance, because we have defined incrementHundred() in our vote-up attribute in the code below, when we call voteUp on our directive it will call incrementHundred() on the parent scope.

AngularJS converts camel-case to snake-case when moving from JavaScript to HTML. That is why we are using voteUp in our JavaScript and vote-up in the HTML.

And for our directive markup, we have an anchor tag that calls voteUp on ng-click, a div that displays the label value and another anchor tag to call voteDown.

<div class="vote-set">
  <a href="#" class="icon-arrow-up" ng-click="voteUp()"></a>
  <div class="number">{{label}}</div>
  <a href="#" class="icon-arrow-down" ng-click="voteDown()"></a>
</div>

And now we have created a vote directive that we are using in three places to increment the vote count with different denominations.

Isolated Expression Scope with Variables

Occasionally we need to pass a value back to the parent controller as a parameter on the expression we have bound to and so this is what we are going to tackle next.

In our previous example we have defined 6 methods to handle the increment and decrement operations but that seems a little verbose. Wouldn't it be nice if we could consolidate those methods and just send in the value that we want to increment or decrement the vote count by?

In the HTML below, we have set things into motion by defining an incrementVote and decrementVote method that accepts unit as a parameter.

<div class="container" ng-controller="MainController">
  <h1>Current Votes: {{votes}}</h1>
  <hr>
  <h3>Isolated Expression Scope with Variables</h3>
  <div class="vote-container">
    <vote label="9" vote-up="incrementVote(unit)" 
      vote-down="decrementVote(unit)"></vote>
    <vote label="6" vote-up="incrementVote(unit)" 
      vote-down="decrementVote(unit)"></vote>
    <vote label="3" vote-up="incrementVote(unit)" 
      vote-down="decrementVote(unit)"></vote>
  </div>
</div>

And in the template markup below, we are going to call voteUp and voteDown and with a value for the parent controller.

<div class="vote-set">
  <a href="#" class="icon-arrow-up" 
    ng-click="voteUp({unit:label})"></a>
  <div class="number">{{label}}</div>
  <a href="#" class="icon-arrow-down" 
    ng-click="voteDown({unit:label})"></a>
</div>

The most important thing to keep in mind when passing variables with isolated expressions is that we do not pass them one-by-one but with a parameter object.

We are going to use the value we defined for the label property as our unit value. And in the MainController, we will take the the unit value and cast it to an integer with parseInt(unit,10) and add or subtract it to $scope.votes.

angular.module('website', [])
  .controller('MainController', ['$scope',
    function($scope) {
      $scope.votes = 0;

      // ...

      $scope.incrementVote = function(unit) {
        $scope.votes += parseInt(unit,10);
      };

      $scope.decrementVote = function(unit) {
        $scope.votes -= parseInt(unit,10);
      };
    }
  ])

Isolated Expression Scope with Multiple Methods

We are going to wrap things up with one more neat thing that you can do with isolated expression scope. We can actually bind more than one method to a single expression on the directive.

For the sake of illustration, we are going to add a vote directive that increments and decrements the vote count by 1000. When this happens, we want to call a second method called alertAuthorities because clearly something is amiss!

<div class="container" ng-controller="MainController">
  <h1>Current Votes: {{votes}}</h1>
  <hr>
  <h3>Isolated Expression Scope with Multiple Methods</h3>
  <div class="vote-container">
    <vote label="1000" 
      vote-up="incrementVote(1000);alertAuthorities();" 
      vote-down="decrementVote(1000);alertAuthorities();">
      </vote>
  </div>
  <hr>
  <div ng-if="alert" class="alert alert-danger">{{alert}}</div>
</div>

We are showing an alert div if there is an alert defined on $scope which we set in the code below.

angular.module('website', [])
  .controller('MainController', ['$scope',
    function($scope) {
      $scope.votes = 0;

      // ...

      $scope.incrementVote = function(unit) {
        $scope.votes += parseInt(unit,10);
      };

      $scope.decrementVote = function(unit) {
        $scope.votes -= parseInt(unit,10);
      };

      $scope.alertAuthorities = function() {
        $scope.alert = 'The authorities have been alerted!';
      };
    }
  ])

By calling voteUp or voteDown on this particular vote directive, we are not only calling incrementVote or decrementVote but alertAuthorities as well.

In Conclusion

We started out with a simple isolated expression scope example and then extended it to pass values from the directive to the parent controller. And then to wrap things up, we learned how we could actually call more than one method on a parent controller using expression isolated scope.

This snippet was written by Lukas Ruebbelke (aka @simpulton) and edited by Ari Lerner (aka @auser).

Hope you enjoyed today's snippet. Stay tuned for tomorrow's and sign up for the newsletter for great Angular content every week.

Enjoy this snippet?

Check out our book that's heading to print this week at ng-book.com

The 600+ page book is packed full of Angular content written and designed to get you up to speed with Angular from beginner to expert.

Independently published with content just like what you've just read.

Brought to you by the team behind ng-newsletter