Combine Angular Directives and Filters to Format UI

I was working on a demo this weekend.  One of the things I wanted to demonstrate is how to format user input in Angular applications.  I am going to use social security number input field to demonstrate the concept.  The idea of Angular filters is a simple one.  They are akin to IValueConverter in XAML applications.  They sit between View (HTML) and Model (models exposed via $scope and controllers).  If you are rendering a read-only view, you can very easily format the output with filters.  First, you have to create a filter.  SSN formatting filter is a simple one.  I am storing only 9 digits in the database, and want to format them with dashes for user to see.  Just to note, I am using TypeScript, but you can easily change the code to plain JavaScript.

angular.module("app.filters", [])
    .filter("ssn", [function () {
        return function (value: string) {
            if (value) {
                if (value.length >= 9) {
                    return value.substr(0, 3) + "-" + value.substr(3, 2) + "-" + value.substr(5);
                } else if (value.length >= 5) {
                    return value.substr(0, 3) + "-" + value.substr(3, 2) + "-" + value.substr(5);
                } else if (value.length >= 3) {
                    return value.substr(0, 3) + "-" + value.substr(3);
                }

            }
            return value;
        };
    }]);

The code is quite simple, not much to explain.  I want to though handle partially completed SSN numbers, and you will see in a minute why.  To use this filter in the HTML output is very easy:

 <strong>SSN:</strong>{{person.SSN | ssn}}

I am passing the actual model value through the SSN filter in the code above.

Doing the same in the user input is a bit harder.  In this case I want to add a directive.  Directive will be attached to the HTML element.  I want to write it in such a way, that it have access to model controller.  This way I can use formatters and parsers to strip the dashes from user input when pushing the data into the model, and also update the UI with re-formatted data.  Parsers and formatters work very mush just like the ones in Windows Forms.  They are run in two distinct use cases.  Parsers are run when user input is pumped into the model. Formatters are run when model is pumped into the HTML views.  Both are collections on model controller.  The order of them does matter.  So, if you are using multiple parsers and formatters on a single element, you want to make sure they are run in proper order.  First, I am going to write the opposite of the first filter, the one that strips dashes back out.

angular.module("app.filters", [])
    .filter("ssnReverse", [function () {
        return function (value: string) {
            if (value) {
                return value.replace(/-/g, "");
            }
            return value;
        };
    }]); 

You can see that the code for reverse filter is very simple. Now. let’s look at the directive.

    export class SSNDirective extends BaseDirective {
        constructor($filter: ng.IFilterService) {
            super();
            var ssnFlter = $filter("ssn");
            var ssnReversedFlter = $filter("ssnReverse");
            this.restrict = "A";
            this.require = ["?ngModel"];
            this.link = function (
                scope: app.core.controllers.ICoreScope, element: ng.IAugmentedJQuery, attributes: DatePickerAttributes, controller: any) {

                if (controller) {
                    var currentController: ng.INgModelController =
                        app.directives.BaseDirective.getControllerFromParameterArray(controller);

                    var formatter = function (value) {
                        return ssnFlter(value);
                    };
                    var parser = function (value) {
                        var formatted = ssnReversedFlter(value);;
                        element.val(ssnFlter(formatted));
                        return formatted;
                    };

                    currentController.$formatters.push(formatter);
                    currentController.$parsers.unshift(parser);
                }
            };
        }
    }

 

    angular.module("app.directives.Common", [])
        .directive("ssn", ["$filter", function ($filter: ng.IFilterService) {
            return new app.directives.SSNDirective($filter);
        }]);

I am injecting filter service into my directive.  This way I do not need to duplicate the filter code in the directive, I can just invoke the filter.  You also see I am creating formatter and parser functions.  Formatter function is simple – it just adds dashes.  Parser function is also simple, it just strips out the dashes by calling my reverse SSN filter.  Both just call the filters I wrote earlier.  The parser also set the value on the element to re-formatted input.

To summarize, you can combine directives and filters to provide nice formatter input for the user data.

Enjoy.

5 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *