Collapsible (help) block directive in Angular - Part 1

Hey there Angular girls & boys, today's simple directive is gonna be for showing inline help blocks. If you are fond of ngAnimate than you'll be able to enhance it the directive with it, here's how the end result could look:

Angular help block directive example

Grab it from github, see a quick demo here or add it as a dependency to your app using bower install ng-help-block --save.

I've been away from Angular for a while, focusing on some Vue.js work for the moment. However, I thought that shouldn't stop me from posting some helpful directives every now and then. By the way, if you are curious about Vue.js, check out this post about starting with Vue from an Angular background.

Motivation

Today's web interfaces are full of distractions & interruptions. Help blocks can contribute to these distractions, especially when it comes to inline forms. A good way to maintain the current context yes to use inline-expandable blocks, which can work very well as an alternative to dialogs.

Building the directive

While planning out such a directive you must consider 3 things:

  • template structure (layout)
  • data that's going to be passed to the directive
  • user interactions (reactiveness)

Let's start with the template.
We'll need a container with 2 blocks: one for the title & one for the content.

<section class="hb-row">  
  <p class="hb-title" ng-bind="title"></p>
  <p class="hb-content" ng-bind="content"></p>
</section>  

We can anticipate that there's going to be a flag that shows/hides the content, therefore we can add it to the content paragraph. To later help with the styling, we can also add a ng-class attribute on the container.

<section class="hb-row" ng-class="expanded: hbVisible">  
  <p class="hb-title" ng-bind="title"></p>
  <p class="hb-content" ng-bind="content" ng-show="hbVisible"></p>
</section>  

The next step is figuring out what data the directive's going to receive. For simplicity's sake let's just keep it to the title and content of the help block. The directive is going to have an isolate scope to enable re-usage across the app. In this case we can pass the data via attributes, for example help-block-title & help-block-content.

angular.module('app', [])  
       .directive('helpBlock', helpBlockDirective);

function helpBlockDirective(){  
  function helpBlockLink(scope, element, attrs) {
    scope.title = attrs.helpBlockTitle;
    scope.content = attrs.helpBlockContent;
  }

  return {
    scope: {},
    restrict: 'A',
    replace: true,
    template: '<section class="hb-row" ng-class="expanded: hbVisible">
                <p class="hb-title" ng-bind="title"></p>
                <p class="hb-content" ng-bind="content" ng-show="hbVisible></p>
              </section>',
    link: helpBlockLink
  };
}

Now let's take care of the user interactions. The logic is fairly simple, pressing the title of the help block will flip a flag that shows/hides the content. First we'll attach a ng-click directive to the help-block's title.

<p class="hb-title" ng-bind="title" ng-click="toggle()"></p>  
angular.module('app', [])  
       .directive('helpBlock', helpBlockDirective);

function helpBlockDirective(){  
  function helpBlockLink(scope, element, attrs) {
    scope.title = attrs.helpBlockTitle;
    scope.content = attrs.helpBlockContent;
    scope.hbVisible = false;

    scope.toggle = function toggleHelpBlock(){         
       scope.hbVisible = !scope.hbVisible;
    };
  }

  ...
}



We're already up and running, but why stop here? To distinguish the help block from other labels we can append an icon to the title. The simplest way to do that is to use a CSS class that we can style later as we please. If using Bootstrap or FontAwesome you can do it super easily by appending an icon-font class. To do that the directive will require a new attribute & the template a new element.

angular.module('app', [])  
       .directive('helpBlock', helpBlockDirective);

function helpBlockDirective(){  
  function helpBlockLink(scope, element, attrs) {
    scope.iconClass = attrs.helpBlockIconClass;
    //...
  }
}
<section class="hb-row" ng-class="{'expanded': hbVisible}">  
  <p class="hb-title">
    <i ng-if="iconClass" class="{{iconClass}}"></i>
    <span ng-bind="title"></span>
  </p>

  <p class="hb-content" ng-bind="content" ng-show="hbVisible"></p>
</section>  

Now we can use the following to generate as many help blocks as we want across our Angular app.

<section help-block help-block-title="{{title}}" help-block-content="{{content}}" help-block-icon-class="fa fa-question-circle"></section>  

Using ngAnimate

I promised ngAnimate can be used to spice things up, here's a simple way to do it. First, remember to add it as a dependency to your app.

angular.module('app', ['ngAnimate']);  

Then, use CSS to create a simple animation for the help block content:

.hb-content{
  position:relative;
}

.hb-content.ng-hide-remove,
.hb-content.ng-hide-add,
.hb-content.ng-hide-add-active,
.hb-content.ng-hide-remove-active {
  transition: all .2s ease
}

.hb-content.ng-hide-add {
  opacity: 1;
  animation: .2s fadeOut
}

.hb-content.ng-hide-remove {
  opacity: 1;
  animation: .2s fadeInUp
}

@keyframes fadeInUp {
  0% {
    opacity: 0;
    bottom: -10px
  }
  100% {
    opacity: 1;
    bottom: 0
  }
}

@keyframes fadeOut {
  0% {
    opacity: 1;
    bottom: 0
  }
  100% {
    opacity: 0;
    bottom: -10px
  }
}

That's it! It's up to you to style the help-block even more, if you're looking for a quick solution grab the full stylesheet here.

Check out the source or a quick example on github too.

Ready to take it to the next level? Make this directive collapse & expand any content you can throw at it. Check out part 2!