*In this post I talk about how to create reusable directives in Angular.js by parameterizing possible differences into attributes*
##### Introduction

It’s a well established principle in programming that duplicating code should be avoided. One of the great advantages of Angular.js is that it allows you to reduce duplication by defining reusable HTML tags that encapsulate both HTML and Angular code. These reusable tags, known as directives, allow you to write much more readable and maintainable code.

Doing this is straight-forward for code that is identical everywhere it is used. However, reducing duplicate code is most effective when you can consolidate similar code despite small differences.

In functions this can be done by pushing the differences into parameters, so that only the similarities are apparent. In Angular directives, the same can be done by passing in data both as full HTML elements and as HTML attributes.

Note:

This is not a tutorial on how to make a directive. If you have never made one before, here is a good guide.

Example Project

Here is a project I made to illustrate this technique. The two pages each have a form for dealing with an item. One creates an item on submit, the other edits an item. Each form has a different title. Also, the form on the edit page has one additional field.

Vertical modules

Directives are supposed to be fully reusable. I like to define directives in vertical modules, so every file needed for a directive to work is included in a single folder. This means defining a controller for each directive and managing the directive only through there. This approach promotes the directive’s encapsulation.

Screen Shot 2014-12-09 at 3.48.58 PM

The Configurable Parts

This is what calling the directive from the create item page looks like

Create Item

There are three forms of configuration in this HTML:

1. <h1>Create Item</h1>

The form title has been passed in through use of ng-transclude. Doing so allows you to define the title as a full HTML element outside the blackbox of the directive. This way someone wanting to do something different with it, say include a smaller title, doesn’t have to change anything inside the directive.

2. submit-text="Create Item"

A different strategy is used for text of the submit button. First of all, you can only transclude elements to one place in the directive. But more importantly, because transclusion is optional, we don’t want to risk a situation where a form gets created without a submit button. By including the button inside the directive and defining it’s text as a variable in the scope (which is placed there by the link function that reads it out of the attributes), we bypass both of these problems

3. method="post"

All the functions for handling submissions are defined as properties of an object inside the directive’s controller. The submit function uses the string we have defined here to choose which function to call. I chose to use the HTTP verbs, but you could choose any names you want.

<code class="prettyprint">//itemFormController.js
var methods = {
    post: function(newItem) {
        $http.post('/item', newItem)
            .success(function(data, status, headers, config) {
                $scope.statusMessage = 'Your item has been created';
                $scope.submitted = false;
            }).error(function(data, status, headers, config) {
                $scope.statusMessage = 'There was an error creating your item';
                deffered.reject(data, status, headers, config);
            });
    },
    put: function(changedItem) {
        $http.put('/item', changedItem)
            .success(function(data, status, headers, config) {
                if (data) {
                    $scope.statusMessage = 'Your edits have been submitted';
                    $scope.submitted = false;
                } else {
                    alert(JSON.stringify(data));
                    deffered.reject(data, status, headers, config);
                }
            }).error(function(data, status, headers, config) {
                console.log('Error when editing item');
            });
    }
};
$scope.submit = function(itemForm) {
    $scope.submitted = true;
    if (itemForm.$valid) {
        methods[$scope.method]($scope.newItem);
    }
};

This value is also used by the form to determine if it should include the id field in the form.

Conclusion

Parameterizing differences into attributes allows you to consolidate your Angular and HTML code into reusable directives. This improves the readability of your code and the maintainability of your app.

Coming up…

One limitation of this strategy is that the data inside your directive can’t be used inside other directives. For example, you could not include list of items that immediately reflected the changes you have made inside the item form. I will be writing about this in a future blog post, stay tuned!