[angularjs] filters on ng-model in an input

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.

The answer is


You can try this

$scope.$watch('tags ',function(){

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

});

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.


I had a similar problem and used

ng-change="handler(objectInScope)" 

in my handler I call a method of the objectInScope to modify itself correctly (coarse input). In the controller I have initiated somewhere that

$scope.objectInScope = myObject; 

I know this doesn't use any fancy filters or watchers... but it's simple and works great. The only down-side to this is that the objectInScope is sent in the call to the handler...


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.


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

for example:

ng-value="price | number:8"

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

If you are doing complex, async input validation it might be worth it to abstract ng-model up one level as part of a custom class with its own validation methods.

https://plnkr.co/edit/gUnUjs0qHQwkq2vPZlpO?p=preview

html

<div>

  <label for="a">input a</label>
  <input 
    ng-class="{'is-valid': vm.store.a.isValid == true, 'is-invalid': vm.store.a.isValid == false}"
    ng-keyup="vm.store.a.validate(['isEmpty'])"
    ng-model="vm.store.a.model"
    placeholder="{{vm.store.a.isValid === false ? vm.store.a.warning : ''}}"
    id="a" />

  <label for="b">input b</label>
  <input 
    ng-class="{'is-valid': vm.store.b.isValid == true, 'is-invalid': vm.store.b.isValid == false}"
    ng-keyup="vm.store.b.validate(['isEmpty'])"
    ng-model="vm.store.b.model"
    placeholder="{{vm.store.b.isValid === false ? vm.store.b.warning : ''}}"
    id="b" />

</div>

code

(function() {

  const _ = window._;

  angular
    .module('app', [])
    .directive('componentLayout', layout)
    .controller('Layout', ['Validator', Layout])
    .factory('Validator', function() { return Validator; });

  /** Layout controller */

  function Layout(Validator) {
    this.store = {
      a: new Validator({title: 'input a'}),
      b: new Validator({title: 'input b'})
    };
  }

  /** layout directive */

  function layout() {
    return {
      restrict: 'EA',
      templateUrl: 'layout.html',
      controller: 'Layout',
      controllerAs: 'vm',
      bindToController: true
    };
  }

  /** Validator factory */  

  function Validator(config) {
    this.model = null;
    this.isValid = null;
    this.title = config.title;
  }

  Validator.prototype.isEmpty = function(checkName) {
    return new Promise((resolve, reject) => {
      if (/^\s+$/.test(this.model) || this.model.length === 0) {
        this.isValid = false;
        this.warning = `${this.title} cannot be empty`;
        reject(_.merge(this, {test: checkName}));
      }
      else {
        this.isValid = true;
        resolve(_.merge(this, {test: checkName}));
      }
    });
  };

  /**
   * @memberof Validator
   * @param {array} checks - array of strings, must match defined Validator class methods
   */

  Validator.prototype.validate = function(checks) {
    Promise
      .all(checks.map(check => this[check](check)))
      .then(res => { console.log('pass', res)  })
      .catch(e => { console.log('fail', e) })
  };

})();

Examples related to angularjs

AngularJs directive not updating another directive's scope ERROR in Cannot find module 'node-sass' CORS: credentials mode is 'include' CORS error :Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response WebSocket connection failed: Error during WebSocket handshake: Unexpected response code: 400 Print Html template in Angular 2 (ng-print in Angular 2) $http.get(...).success is not a function Angular 1.6.0: "Possibly unhandled rejection" error Find object by its property in array of objects with AngularJS way Error: Cannot invoke an expression whose type lacks a call signature

Examples related to angularjs-filter

Angular.js ng-repeat filter by property having one of multiple values (OR of values) Using AngularJS date filter with UTC date Limit the length of a string with AngularJS Descending order by date filter in AngularJs ng-repeat :filter by single field filters on ng-model in an input

Examples related to dom-manipulation

How to move all HTML element children to another parent using JavaScript? filters on ng-model in an input How to clear all <div>s’ contents inside a parent <div>? How to add a class to a given element?

Examples related to angularjs-ng-model

Angular CLI - Please add a @NgModule annotation when using latest ng-model for `<input type="file"/>` (with directive DEMO) AngularJS: ng-model not binding to ng-checked for checkboxes filters on ng-model in an input Difficulty with ng-model, ng-repeat, and inputs