AngularJS - Building Zippy

Let’s create a zippy that will combine some of the concepts we’ve learned so far for us. Let’s setup our index.html to include our app and an empty zippy tag:

<div ng-app="app">
  <zippy></zippy>
</div>

Now, we need to set up our directive. Let’s start simple by making it say “Hello world” to make sure everything’s working:

var app = angular.module("app", [])

app.directive("zippy", function(){
  return {
    restrict: "E",
    template: '<div>Hello world</div>'
  };
});

For our zippy, there will be a title and some content. When you click on the title it should toggle showing/hiding the content. Let’s update our markup to reflect that. The title will be an attribute on the zippy and it will be set through user input. There will be content inside the zippy, part of which can also be user generated:

<div ng-app="app">
  <input type="text" ng-model="model.title">
  <input type="text" ng-model="model.content">

  <zippy title="{{model.title}}">
    The content is: {{model.content}}
  </zippy>
</div>

Now we need to update our component so that the title is actually wired up. We need to set up title in an isolate scope “@” to read from the attribute we’re passing it. Then we can update our template to display the title.

app.directive("zippy", function(){
  return {
    restrict: "E",
    scope: {
      title: "@"
    },
    template: '<div><h3>{{title}}</h3><div>Hello world</div></div>'
  };
});

We’ll need to add an ng-click attribute to our title (h3) for toggling the content. Then, we’ll create a linking function to be wired up. The linking function provides us with the scope where we can set a boolean isContentVisible, and the toggleContent function which toggles this isContentVisible boolean. Finally, we need to add an ng-show attribute to our content div and pass it this isContentVisible boolean.

app.directive("zippy", function(){
  return {
    restrict: "E",
    scope: {
      title: "@"
    },
    template: '<div>' +
      '<h3 ng-click="toggleContent()">{{title}}</h3>' +
      '<div ng-show="isContentVisible">Hello world</div>' +
      '</div>',
    link: function(scope){
      scope.isContentVisible = false;
      scope.toggleContent = function(){
        scope.isContentVisible = !scope.isContentVisible;
      };
    };
  };
});

If we refresh and type a title, we can click on the title and we’ll see “Hello world”. This, however, isn’t the content that we set inside of our zippy directive and we’ll see that the second input box does nothing. This is because we don’t have transclusion enabled, so anything inside our zippy directive gets overridden by our template. Let’s set up transclusion for our directive. We can do so by returning the transclude property as true and setting the ng-transclude attribute on our “Hello world” div. We can also remove the “Hello world” now.

app.directive("zippy", function(){
  return {
    restrict: "E",
    transclude: true,
    scope: {
      title: "@"
    },
    template: '<div>' +
      '<h3 ng-click="toggleContent()">{{title}}</h3>' +
      '<div ng-show="isContentVisible" ng-transclude></div>' +
      '</div>',
    link: function(scope){
      scope.isContentVisible = false;
      scope.toggleContent = function(){
        scope.isContentVisible = !scope.isContentVisible;
      };
    };
  };
});

Now if we reload, we’ll see if we type in a title and some content, we can click on the title that appears and it will toggle the content beneath it.