Search code examples
angularjsangularjs-directiveangular-ngmodel

How to update both model and view value in Angular directive?


Apologies in advance, directives are not my strong suit!

I have a simple attribute-only directive, the purpose of which is to automatically convert a string in a field to an HH:mm format upon blur'ing the field. This is the directive:

(function () {

    'use strict';

    angular
        .module('app.format-as-time')
        .directive('formatAsTime', timeDirective);

    timeDirective.$inject = [
        'isValid'
    ];

    function timeDirective (isValid) {

        return {
            require: 'ngModel',
            restrict: 'A',
            link: LinkFunction
        };

        function LinkFunction (scope, elem, attrs, ngModel) {

            elem.bind('blur', function () {

                var currentVal = ngModel.$modelValue,
                    formattedVal = '';

                // Format something like 0115 to 01:15
                if (currentVal.length === 4) {
                    formattedVal = currentVal.substr(0, 2) + ':' + currentVal.substr(2, 2);

                // Format something like 115 to 01:15
                } else if (currentVal.length === 3) {
                    formattedVal = '0' + currentVal.substr(0, 1) + ':' + currentVal.substr(1, 2);

                // Format something like 15 to 00:15
                } else if (currentVal.length === 2) {
                    formattedVal = '00:' + currentVal;
                }

                // If our formatted time is valid, apply it!
                if (isValid.time(formattedVal)) {
                    scope.$applyAsync(function () {
                        ngModel.$viewValue = formattedVal;
                        ngModel.$render();
                    });
                }

            });
        }

    }

}());

And the associated view:

<div ng-controller="TestController as test">
    <input type="text"
           maxlength="5"
           placeholder="HH:mm"
           ng-model="test.startTime"
           format-as-time>
    <button ng-click="test.getStartTime()">Get Start Time</button>
</div>

And the associated Controller:

(function () {

    'use strict';

    angular
        .module('app.testModule')
        .controller('TestController', TestController);

    function TestController () {

        var vm = this;

        vm.startTime = '';

        vm.getStartTime = function () {
            console.log(vm.startTime);
        }

    }

}());

At present, the directive works as expected for the view but the model in my controller does not get updated, i.e. the input will contain 01:15 but the model will console.log() 115.

I have tried using:

scope: {
    ngModel: '='
}

in the directive but this did not do anything.

Have I done this the right way, and if so what do I need to add to ensure both the model and view remain in sync?

If I have done this the wrong way, what would be the best way to do it correctly?


Solution

  • The problem lies in this line ngModel.$viewValue = formattedVal; Angular has a pipeline used to set a modelValue which includes running it through registered $parsers and $validators. The proper way to set the value is by calling $setViewValue(formattedVal) which will run the value through this pipeline.