Thursday, March 5, 2015

Instead of ng-include, try an AngularJS directive

Recently, I came across a lot of code in an AngularJS project I'm working on that uses ng-include. I haven't really used ng-include in Angular very much. Instead, whenever I need to reuse a chunk of HTML, I just use create a custom AngularJS directive. Looking back at how I’ve been using directives, a ng-include might have been a better choice in some places.

With that being said, I still think that in most situations, you are better off splitting up functionality into directives. Many of the places in the current Angular project I’m working on where ng-include is being used, a directive would have been a much better choice. So how to know when to use one or the other? One big red flag is if you are thinking of using the ng-init, ng-controller attributes. These have their uses, but, I believe should be avoided when building your application.

Here is and example of what I mean:
<!-- app/views/properties.html -->
<div class="properties" ng-controller="propertiesCtrl">
    <form class="form-horizontal">
        <div class="form-group" 
             ng-repeat="key in currentElement.props" 
             ng-include="'app/views/properties/main.html'"></div>
    </form>
</div>
<!-- app/views/properties/main.html -->
<label class="col-md-3 control-label">
    {{key | capitalize}} :
</label>
<div class="col-md-7"
     ng-include="'composer/flowDesigner/views/properties/controls/DEFAULT.html'">
</div>

The code above is basically the reason why directives were created in Angular. A directive allows you to create a chuck on code that can be expressed as a tag, a pseudo web component if you will. Directives also allows us to isolate functionality and reduce the amount of code that is in a controller for a view or page.

Here is the example above but written as a directive with some comments about how the code is structured:
angular("mainApp")
    // We define the name of the directive with a prefix. The prefix helps to 
    // distinguish our custom html directive tags from actual html tags
    // example : <com-header> vs <header>
    //
    // NOTE: Angular converts camel case to dash case when rendering directives. 
    //       So in our html page, we will use <com-properties> instead of <comProperies>
    .directive("comProperies", function () {
        "use strict";

        return {
            // restrict how this directive can be used, E means as an html tag only
            restrict: "E",
 
            // replace the tag on the page with the template of this directive
            replace: true, 

            // path to our html template
            templateUrl: "app/views/comProperies.tpl.html",

            // html to JS linking function
            link: properiesLink,

            // this directive's controller function
            // NOTE: Start with a capital letter since angular uses controller like 
            //       pseudo classes
            controller: ProperiesCtrl
        };
  
        function properiesLink($scope, $element, $attrs) {
            // The linking function allows us to add any html manipulation we might need.
            // Basically, if you are using jQuery "$" selector function for anything, 
            // add it here.
            //
            // NOTE: Try to avoid using a linking function. Angular has a lot of directives 
            //       you can add to your template file so you don't need one. 
            //       If you are doing anything with the $attrs object, use $attrs.$observe. 
            //       This will trigger a function if the value of an attribute ever changes.
            //       $element is a jQuery reference to your template's root html element.
        }
  
        function ProperiesCtrl($scope) {
            // A basic controller function for this directive. One thing to remember is that 
            // all instances of this directive use the same controller. So, for example, 
            // "this.currentIndex = 0;" would be a global variable among all 
            // <com-properties> elements.
        }
    });

Not too much has changed in this example except for code structure. The real advantage can be seen as you start to expand functionality of this directive. For example, this directive looks like it creates a form and lists out the properties in currentElement.props so that they can be edited. Now we want to list out the properties of currentPlan.details. By re-using the “ng-model” attribute that is provided by angular. We can update the custom directive html element so it can be written as:
<com-properties ng-model=”currentPlan.details”></com-properties>

To make this update, first inside our template, find and change ng-repeat="key in mainTabKeys" to ng-repeat="key in ngModel". Next, add scope : {ngModel : "=" } to the return object in the directive so now our directive looks like this:
angular("mainApp")
    .directive("comProperies", function () {
        "use strict";

        return {
            restrict: "E",
            replace: true, 
            templateUrl: "app/views/comProperies.tpl.html",
            link: properiesLink,
            controller: ProperiesCtrl,
            scope : { 
                ngModel : "=" 
            }
        };
        
        // ...
    });

With this change, this directive will now list out all the properties of whatever object the attribute ng-model is set to. I encourage you to read more about directive by following the links below, since this article is a very limited overview of how directives are used in AngularJS.

No comments:

Post a Comment