Better view code in AngularJS

View code tends to get messy and not reusable.

Here are some tactics to make your view code more readble, testable, reusable and less prone to errors:


1. Isolated directives

Directives are the most common way to reuse view code in AngularJS.

To make a directive very reusable, make sure it:
  • Has an isolated scope
  • Has as few dependencies as possible
  • Has a single responsibility
Examples:
  • profile-pic
    • Input: user object
    • Purpose: display the user’s profile picture that links to his account.
  • time-ago
    • Input: a date object
    • Purpose: format a date as a dynamic “ago” string that updates as time goes by (a few seconds ago, 3 minutes ago, September 2, etc.)
  • on-enter-press
    • Input: callback
    • Purpose: invoke the callback when the element is in focus, and the user presses enter



2. Restrict your directive

In Angular, it's possible to restrict a directive to an element, as follows:

restrict: ‘E’  

This will make your directive more noticeable, thus lowering the risk of someone accidentally deleting or copying the code that uses the directive.

Note: Angular 2.0 will force us to declare which directives we are enabling in our template.

The goes for using external directives as well. In this example:

<tab name="'home'"></tab>  

Is more noticeable that we're using a directive than in here:

<div tab="'home'"></div>  

Restrict to a specific type of element.
In cases, your directive assumes a specific type of element, like an input tag, or a select tag.

While this is not supported in the API (until Angular 2.0), you can restrict the user to using the directive only on the desired TAGs:

compile: function(element, attrs) {  
    if (element[0].tagName !== ‘INPUT’) {
        message = "you can only use this directive on input tags”;
        console.error(message);
        return;
    }
    return function($scope, element, attrs) {
        // return usual link function
    };
}



3. ng-include && script tags

ng-include allows you to keep your HTML code divided into separate pieces of markup.

In many cases, using ng-include is much simpler and straight-forward than using directives.

However, included partials that access scope attributes are risky to reuse.

In some cases, it is better to keep the partial in a script tag inside the same file.

Let’s examine this code:

<div class=”buttons”>  
    <a class=”save” ng-click=”save()” ng-disabled=”isSaveButtonDisabled()”>Save</a>
    <a class=”cancel” ng-click=”cancel()” ng-disabled=”isCancelButtonDisabled()”>Cancel</a>
</div>  
<form>  
    ...
</form>  
<div class=”buttons”>  
    <a class=”save” ng-click=”save()” ng-disabled=”isSaveButtonDisabled()”>Save</a>
    <a class=”cancel” ng-click=”cancel()” ng-disabled=”isCancelButtonDisabled()”>Cancel</a>
</div>  

Here we have a set of buttons at the top and the bottom of the form.
Let’s reuse the code for the form buttons using ng-include

<ng-include src=”’formButtons.html’”></ng-include>  
<form>  
   …
</form>  
<ng-include src=”’formButtons.html’”></ng-include>

<script type="text/ng-template" id="formButtons.html">  
    <div class=”buttons”>
        <a class=”save” ng-click=”save()” ng-if=”” ng-disabled=”isSaveButtonDisabled()”>Save</a>
        <a class=”cancel” ng-click=”cancel()” ng-if=”!isNew” ng-disabled=”isCancelButtonDisabled()”>Cancel</a>
    </div>
</script>  

This is very readble and straight-forward.

Defining the partial inside the same file lowers the risk of someone reusing it elsewhere.

Also, this way the html is inserted into the template cache without doing any outbound HTTP calls.

If you want to place partials in a separate HTML file, you can preload them into the template cache using gulp-html2js, and improve load-time performance.

Tip:
Don’t forget the 2 single-quotes around the template’s name when using ng-include.



4. Encapsulate long expressions in a function

While it is tempting to have actual code inside the HTML, it creates messy HTML, and untestable code.

Plus, changing the common behaviour will require to change it everywhere, instead of in a single place.

For example, imagine an accordion component. Each section is toggled individually.
You decide to manage it inside the view with booleans:

<section ng-class=”{open: isOpen.first}” ng-click=”isOpen.first = !isOpen.first”>  
   a section
</section>  
<section ng-class=”{open: isOpen.second}” ng-click=”isOpen.second = !isOpen.second”>  
   another section
</section>  

Now your capricious product manager decides he wants the accordion to have only one section open at all times.
You change the code to this:

<section ng-class=”{open: openSection == ‘first’}” ng-click=”openSection = (openSection == ‘first’ && null || ‘first’)”>  
   a section
</section>  
<section ng-class=”{open: openSection == ‘second’}” ng-click=”openSection = (openSection == ‘second’ && null || ‘second’)”>  
   another section
</section>  

Holy crap.

This is bad because:
  • You need to repeat the same change in several places
  • The HTML code is much less readable
  • The code is not unit-testable

A better way to accomplish it is to leave all logic, even smallest, to the controller:

<section ng-class=”{open: vm.isOpen(‘first’)}” ng-click=”vm.toggle(‘first’)”>  
   a section
</section>  
<section ng-class=”{open: vm.isOpen(‘second’)}” ng-click=”vm.toggle(‘second’)”>  
   another section
</section>  

The controller would look like this:

this.toggle = function (section) {  
    if (this.isOpen(section)) {
        this.curSection = null;                     
    }
    else {
        this.curSection = section;
    }
}
this.isOpen = function (section) {  
    this.curSection = section;
}

Now you have reusable, testable and readable functions. Kudos!


5. Dynamic partials

Sometimes you need to display different content based on a property of an object.

For example:

<div class="order">  
    <h1>{{order.title}}</h1>
    <div ng-if="order.status=='pending_payment'">
        ...
    </div>
    <div ng-if="order.status=='cancelled'">
        ...
    </div>
    <div ng-if="order.status=='done'">
        ...
    </div>
    ...
    <button>Back</button>
</div>  

While the general layout is the same, the inner content is completely different per status.


A nice way to refactor this code would be:

<div class="order">  
    <h1>{{order.title}}</h1>
    <ng-include src="'partials/order_' + order.status + '.html'">
        ...
    </ng-include>
    ...
    <button>Back</button>
</div>  

And have the specific code in the following partials:

  • partials/order_pending_payment.html
  • partials/order_cancelled.html
  • partials/order_done.html

Although this is a little more implicit, it allows us to keep our files small, readable and single-purposed.