2

I am coding a filter that will format phone numbers in a contact form I've built however for some reason the value in the input is never being updated and I'm not sure what I'm doing wrong.

Here's my HTML:

<div class='form-group'>
    <input name='phone' ng-model='home.contact.phone' placeholder='(Area code) Phone' required ng-bind='home.contact.phone | phone' />
</div>

and here's my filter:

(function () {
    'use strict'

    angular
        .module('Allay.phoneFilter', [])
        .filter('phone', function () {
            return function (phone) {
                if(!phone) return '';

                var res = phone + '::' // Since this isn't working, I'm doing something super simple, adding a double colon to the end of the phone number.
                return res;
            }
        });
})();

I'm not sure if you need this, but here's the controller:

(function () {
    'use strict'

    angular
        .module('Allay', [
            'Allay.phoneFilter'
        ])
        .controller('HomeController', function () {
            var home = this;
        });
})();

If I add an alert(res) before 'return res' in the filter I see the value I expect '123::', however the value in the input it's self is still just 123.

2
  • Where would I do that? I haven't seen anything in the docs about needing $scope.$apply() with a filter. Commented Oct 20, 2015 at 21:04
  • Ok, since you just changed the content of the question to invalidate my answer, I will leave this with the assumption that one of the other answers will help. Commented Oct 20, 2015 at 21:13

4 Answers 4

1

You need create directive to change your ngModel, like this:

.directive('phoneFormat', function() {
     return {
         require: 'ngModel',
         link: function(scope, elem, attrs, ctrl) {

             var setvalue = function() {
                 elem.val(ctrl.$modelValue + "::");
             };

             ctrl.$parsers.push(function(v) {
                 return v.replace(/::/, '');
             })


             ctrl.$render = function() {
                 setvalue();
             }

             elem.bind('change', function() {
                 setvalue();
             })

         }
     };
 });

Use in html:

<input name='phone' ng-model='contact.phone' placeholder='(Area code) Phone' required phone-format />

JS Fiddle: http://jsfiddle.net/57czd36L/1/

Sign up to request clarification or add additional context in comments.

7 Comments

It's not necessary to create a directive, his approach using filters was right.
@Cyberdelphos here is your solution jsfiddle.net/wuxfurdn and in console is error - Non-assignable model expression: contact.phone | phone (<input name="phone" ng-model="contact.phone | phone" placeholder="(Area code) Phone" required="">)
I thought the idea was to use directives to modify the DOM behavior or add behavior to the controller and to use a filter when transforming a value such as I am doing.
I tried this however it doesn't appear to watch the home.contact.phone for changes as the :: is only added to the initialized value and isn't added when you edit the value.
Since it is an input, then a directive is needed, you are right Bartosz
|
1

Your usage of ngBind on the input is not quite correct. From the documentation,

The ngBind attribute tells Angular to replace the text content of the specified HTML element with the value of a given expression, and to update the text content when the value of that expression changes

You do not need to replace the text content of the <input> element, that wouldn't make sense. You can instead extend the formatter pipeline of the NgModelController using a directive like

app.directive('phoneFormat', function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ngModel) {
            ngModel.$formatters.push(function (value) {
                if (value)
                    return value + '::';
            });
        }
    }
});

Then, in your HTML,

<input ng-model='home.contact.phone' phone-format />

In case you wanted to keep the filter you wrote (for other usages), you can actually re-use it in the directive like

app.directive('phoneFormat', [ '$filter', function ($filter) {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ngModel) {
            ngModel.$formatters.push($filter('phone'));
        }
    }
}]);

$filter('phone') simply returns the filter function registered under 'phone'. Here is a Plunker.


Note, this solution will only format data when you change the $modelValue of the NgModelController, for example like

$scope.$apply('home.contact.phone = "123-456-7890"');

If you are looking for something to update/format the value of the input as the user is typing, this is a more complicated task. I recommend using something like angular-ui/ui-mask.

1 Comment

@xRoyDot, Agree with you, that is a correct direction to go. But "masking" an input value as the user is typing is a notoriously difficult task to do well. When done improperly, it is very very annoying to the user. For example, you have to worry about preserving cursor position when editing the middle of the input. ui-mask does do the task well.
1

Although a filter module is a good approach, I use an 'A' directive to do the dirty work because changing the element value will affect its ng-model.

However, I would only suggest this kind of solution if your actual data manipulation could sum in 3-4 lines of code; otherwise, a more thorough approach is needed. This is an example that will delete anything which isn't an integer:

(function () {
    'use strict'
    angular.module('Allay').directive('phoneValidator', function () {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                angular.element(element).on('keyup', function() {
                    element.val(element.val().replace(/[^0-9\.]/, ''));
                });
            }
        }
    });
})();

And than in your HTML template :

<input name="phone" ng-model="home.contact.phone" placeholder="(Area code) Phone" phoneValidator required/>`

Comments

-2

You should remove your "ng-bind" cause you are filtering it and what is presented is what in the ng-model. use value instead.

    <input name='phone' ng-model='home.contact.phone | phone' value="{{contact.phone | phone}}"  />

see working example: JsFiddle

4 Comments

He has already changed/corrected the double module definition in his question... so only your first point applies here...
For some reason the input in your example can't be changed. It's fixed to whatever value it's init with.
I see that it works using $scope which is odd, but regardless angular best practice says not to use $scope in this way, hence the var home = this; (best practice)
@efarley - updated my Fiddle. now its not using $scope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.