9 October 2013

How to Create a Configurable AngularJS Directive

In our CampNG class, we have a section on adding validations and error messages. Having to add a span for each possible validation error is pretty tedious, so I thought I’d have a go at writing a directive to do it. One thing that was interesting about building it was making it configurable. I haven’t seen much info out there about how to do this, so I thought I’d talk about how I did it.

The validation directives in Angular gives us an $error object on each input with a key value pair for each validation. The key is the type of validation and the value is a boolean with true indicating a validation error. What we want is to take this object and produce an error message for each validation and display it if that validation is failing. We’d also like to be able to configure the error message for each validation. The way to do this is to have our directive depend on an ErrorMessages service which is in charge of grabbing the error message for a given validation. The directive code looks like this:

angular.module("validationErrors").
  directive('validationErrors', function(ErrorMessages) {
    return {
      restrict: "A",
      scope: {
        errors: "=",
        errorClass: "@"
      },
      controller: function($scope) {
        $scope.errorFor = function(errKey) { return ErrorMessages.error(errKey);};
        $scope.buildErrorClass = function() { return this.errorClass || "inline-help text-error"; }
      },
      template: "<span ng-class='buildErrorClass()' ng-repeat='(errorKey, isError) in errors' ng-show='isError'>{{errorFor(errorKey)}}</span>"
    };
  });

The next trick is to make our ErrorMessages service configurable. Turns out we need to use a provider to do this. Defining a provider is just a more verbose way to define a factory or service. In fact, both factory and service delegate to provider as described in this excellent post. But providers can do something services and factories can’t: they can be injected into a config block. This allows us to define a property on our provider that holds the messages and then configure them in our application. Here’s what our provider looks like:

angular.module("validationErrors").provider("ErrorMessages", function() {

    this.errorMessages = {};

    this.$get = function() {
        var errorMessages = this.errorMessages;
        return {
            error: function(errorType) {
                return errorMessages[errorType] || errorType;
            }
        };
    };

    this.setErrorMessages = function(messages) {
        this.errorMessages = messages;
    };
});

Notice the $get method. This is where we define what our provider returns when ErrorMessages is injected. In our application’s module config, we can have ErrorMessagesProvider injected and pass it the messages for each validation. This is what it ends up looking like:

var app = angular.module('plunker', ["validationErrors"]).config(function(ErrorMessagesProvider) {
  ErrorMessagesProvider.setErrorMessages({required: "Ya gotta have it!"});
});

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
});

We define our application module, listing the validationErrors module as a dependency. This allows us to have ErrorMessagesProvider passed to us in the config block and set our error messages. The GitHub repo for this directive is here, and here’s the obligatory plunk showing it’s use. Last but not least, I feel obligated to point out that we love building rich client front end web apps, and if you need some help let’s talk.

Heads up! This article may make reference to the Gaslight team—that's still us! We go by Launch Scout now, this article was just written before we re-introduced ourselves. Find out more here.

Related Posts

Want to learn more about the work we do?

Explore our work

Ready to start your software journey with us?

Contact Us