filters on ng-model in an input

220,281

Solution 1

I would suggest to watch model value and update it upon chage: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

The only interesting issue is with spaces: In AngularJS 1.0.3 ng-model on input automatically trims string, so it does not detect that model was changed if you add spaces at the end or at start (so spaces are not automatically removed by my code). But in 1.1.1 there is 'ng-trim' directive that allows to disable this functionality (commit). So I've decided to use 1.1.1 to achieve exact functionality you described in your question.

Solution 2

I believe that the intention of AngularJS inputs and the ngModel direcive is that invalid input should never end up in the model. The model should always be valid. The problem with having invalid model is that we might have watchers that fire and take (inappropriate) actions based on invalid model.

As I see it, the proper solution here is to plug into the $parsers pipeline and make sure that invalid input doesn't make it into the model. I'm not sure how did you try to approach things or what exactly didn't work for you with $parsers but here is a simple directive that solves your problem (or at least my understanding of the problem):

app.directive('customValidation', function(){
   return {
     require: 'ngModel',
     link: function(scope, element, attrs, modelCtrl) {

       modelCtrl.$parsers.push(function (inputValue) {

         var transformedInput = inputValue.toLowerCase().replace(/ /g, ''); 

         if (transformedInput!=inputValue) {
           modelCtrl.$setViewValue(transformedInput);
           modelCtrl.$render();
         }         

         return transformedInput;         
       });
     }
   };
});

As soon as the above directive is declared it can be used like so:

<input ng-model="sth" ng-trim="false" custom-validation>

As in solution proposed by @Valentyn Shybanov we need to use the ng-trim directive if we want to disallow spaces at the beginning / end of the input.

The advantage of this approach is 2-fold:

  • Invalid value is not propagated to the model
  • Using a directive it is easy to add this custom validation to any input without duplicating watchers over and over again

Solution 3

A solution to this problem could be to apply the filters on controller side :

$scope.tags = $filter('lowercase')($scope.tags);

Don't forget to declare $filter as dependency.

Solution 4

If you are using read only input field, you can use ng-value with filter.

for example:

ng-value="price | number:8"

Solution 5

Use a directive which adds to both the $formatters and $parsers collections to ensure that the transformation is performed in both directions.

See this other answer for more details including a link to jsfiddle.

Share:
220,281
Andrew WC Brown
Author by

Andrew WC Brown

I'm from Thunder Bay but I've lived a few places so you may have encountered me in Barcelona, Paris, Amsterdam, Toronto, London or Halifax. I'm a full-stack developer of 10+ years.

Updated on February 17, 2022

Comments

  • Andrew WC Brown
    Andrew WC Brown about 2 years

    I have a text input and I don't want to allow users to use spaces, and everything typed will be turned into lowercase.

    I know I'm not allowed to use filters on ng-model eg.

    ng-model='tags | lowercase | no_spaces'
    

    I looked at creating my own directive but adding functions to $parsers and $formatters didn't update the input, only other elements that had ng-model on it.

    How can I change the input of that I'm currently typing in?

    I'm essentially trying to create the 'tags' feature that works just like the one here on StackOverflow.

  • Andrew WC Brown
    Andrew WC Brown about 11 years
    This was exactly what I looking for. It turns out I'm already using angularjs 1.1.1
  • Mark Rajcok
    Mark Rajcok about 11 years
    @Valentyn, your solution applied to SO question I referenced in the comment above. Thanks. stackoverflow.com/questions/12176925/…
  • Valentyn Shybanov
    Valentyn Shybanov about 11 years
    I am sure that tricky part was with modelCtrl.$setViewValue(transformedInput); modelCtrl.$render(); Useful would be link to documentation: docs.angularjs.org/api/ng.directive:ngModel.NgModelControlle‌​r One word to "protect" my solition is that scope property could be changed not only from views and my way cover this. So I think it depends on a actual situation how scope could be modified.
  • GSto
    GSto almost 11 years
    what does 'modelCtrl' refer to in your example?
  • Dofs
    Dofs almost 11 years
    Where do you get the inputValue from?
  • Vignesh PT
    Vignesh PT over 10 years
    This is really awesome! Worked for me. Thanks
  • Mikhail
    Mikhail about 10 years
    The best solution ever!
  • Nate-Wilkins
    Nate-Wilkins about 10 years
    @GSto modelCtrl is the controller required by the directive. (require 'ngModel')
  • Nate-Wilkins
    Nate-Wilkins about 10 years
    @Dofs The input value is passed in when the model value changes - see github.com/angular/angular.js/blob/master/src/ng/directive/…
  • Mr Mikkél
    Mr Mikkél almost 10 years
    But you'd need a $watch on it if you want it to update properly.
  • holmesal
    holmesal over 9 years
    Nice! This should be the accepted answer - keeping invalid data out the model is far better than watching for changes and retroactively updating. Great answer.
  • pilavdzice
    pilavdzice over 9 years
    this solution can have bad side effects, see other answer below, you should use a directive for this
  • BorisOkunskiy
    BorisOkunskiy over 9 years
    Reassigning scope variable from within $watch forces the listener to be invoked again. In simple cases (where your filter is idempotent) you will end up with the filter executing twice on every modification.
  • Hafez Divandari
    Hafez Divandari about 9 years
    Cursor jumps to the end of the textfield each time you type an invalid character, try to write 'world' and modify it to 'HeLLo world'!
  • Geuis
    Geuis almost 9 years
    Sorry to add to a 2+ year answer but you can also set attrs.ngTrim = 'false' in the link() function and thus avoid needing to add the attribute to the element. Seems it has to be a string, not the boolean value.
  • icfantv
    icfantv about 8 years
    this is only executed once. and adding to a watch is not the right solution because it, even initially, allows the model to become invalid - the correct solution is to add to the model's $parsers.
  • icfantv
    icfantv about 8 years
    You don't have to like my answer, but that doesn't mean it's wrong. Check your ego before you downvote.
  • Jens Gabe
    Jens Gabe over 6 years
    That is exactly what the built in ngList directive does
  • DeLac
    DeLac about 6 years
    Thank you. Is it possibile to have a similar thing, without modify the model? I mean, i'd like to use the filter to show the input number as currency, without modify the model into a string
  • ksadowski
    ksadowski over 3 years
    +1 This may not be an answer to the exact question that was asked, but including it is helpful to others whose situation could be slightly different.
  • feltspar
    feltspar about 2 years
    so does this work as we type in the input? Or do we need to add a watcher in the directive?
  • feltspar
    feltspar about 2 years
    ignore my last comment. Just learned formatters and parsers are pipeline functions which execute when ng-model changes. The solution presented works for me using $formatters. Including solution as one of the answers. Letting the previous comment be for some lost dev like me.
  • feltspar
    feltspar about 2 years
    How do we write a unit test for this?