фильтры по ng-модели на входе


124

У меня есть текстовый ввод, и я не хочу, чтобы пользователи использовали пробелы, и все набранное будет переведено в нижний регистр.

Я знаю, что мне не разрешено использовать фильтры на ng-модели, например.

ng-model='tags | lowercase | no_spaces'

Я посмотрел на создание своей собственной директивы, но добавлял функции $parsersи $formattersне обновлял ввод, а только другие элементы, которые были ng-modelна нем.

Как я могу изменить ввод того, что я сейчас набираю?

По сути, я пытаюсь создать функцию «теги», которая работает так же, как здесь, в StackOverflow.


Посмотрите, помогает ли использование $ timeout (..., 0) с ng-change: stackoverflow.com/questions/12176925/…
Марк Райкок,

Ответы:


28

Я бы посоветовал посмотреть стоимость модели и обновить ее при изменении: http://plnkr.co/edit/Mb0uRyIIv1eK8nTg3Qng?p=preview

Единственная интересная проблема связана с пробелами: в AngularJS 1.0.3 ng-model на входе автоматически обрезает строку, поэтому он не обнаруживает, что модель была изменена, если вы добавляете пробелы в конце или в начале (поэтому пробелы не удаляются автоматически моим код). Но в 1.1.1 есть директива ng-trim, позволяющая отключить эту функцию ( фиксацию ). Поэтому я решил использовать 1.1.1 для достижения точной функциональности, которую вы описали в своем вопросе.


Это было именно то, что я искал. Оказывается, я уже использую angularjs 1.1.1
Эндрю Браун

@Valentyn, ваше решение применимо к вопросу SO, на который я ссылался в комментарии выше. Спасибо. stackoverflow.com/questions/12176925/…
Марк Райкок

это решение может иметь плохие побочные эффекты, см. другой ответ ниже, для этого следует использовать директиву
pilavdzice

Переназначение переменной области видимости изнутри $watchприводит к повторному вызову слушателя. В простых случаях (когда ваш фильтр идемпотентен) вы закончите тем, что фильтр будет выполняться дважды при каждой модификации.
воплотился

204

Я считаю, что намерение входов и ngModelдирективы AngularJS состоит в том, что недопустимые входные данные никогда не должны попадать в модель . Модель всегда должна быть действующей. Проблема с недопустимой моделью заключается в том, что у нас могут быть наблюдатели, которые запускают и предпринимают (несоответствующие) действия на основе недопустимой модели.

На мой взгляд, правильное решение здесь - подключиться к $parsersконвейеру и убедиться, что недопустимый ввод не попадает в модель. Я не знаю , как вы пытались приблизиться к вещи или что конкретно не работать для вас , $parsersно здесь это простая директива , которая решит проблему (или , по крайней мере , в моем понимании проблемы):

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;         
       });
     }
   };
});

Как только указанная выше директива объявлена, ее можно использовать так:

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

Как и в решении, предложенном @Valentyn Shybanov, нам нужно использовать ng-trimдирективу, если мы хотим запретить пробелы в начале / конце ввода.

Преимущество такого подхода двоякое:

  • Недействительное значение не распространяется на модель
  • Используя директиву, легко добавить эту настраиваемую проверку к любому входу, не дублируя наблюдателей снова и снова.

1
Я уверен, что сложная часть была modelCtrl.$setViewValue(transformedInput); modelCtrl.$render();связана с полезной ссылкой на документацию: docs.angularjs.org/api/ng.directive:ngModel.NgModelController Одним словом для «защиты» моего решения является то, что свойство области можно изменять не только из представлений и мой способ прикрыть это. Поэтому я думаю, что то, как можно изменить объем, зависит от реальной ситуации.
Валентин Шибанов

2
на что ссылается 'modelCtrl' в вашем примере?
GSto

4
Откуда вы берете inputValue?
Dofs

2
@GSto modelCtrl- это контроллер, требуемый директивой. ( require 'ngModel')
Nate-Wilkins

7
Курсор переходит в конец текстового поля каждый раз, когда вы вводите недопустимый символ, попробуйте написать «мир» и изменить его на «HeLLo world»!
Хафез Дивандари

23

Решением этой проблемы может быть применение фильтров на стороне контроллера:

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

Не забудьте указать $filterкак зависимость.


4
Но вам понадобятся часы $, если вы хотите, чтобы он обновлялся должным образом.
Mr Mikkél

это выполняется только один раз. и добавление к часам - неправильное решение, потому что оно даже на начальном этапе позволяет модели стать недействительной - правильное решение - добавить в парсеры $ модели.
icfantv

4
Вам не обязательно нравиться мой ответ, но это не значит, что он неправильный. Проверьте свое эго, прежде чем голосовать против.
icfantv

6

Если вы используете поле ввода только для чтения, вы можете использовать ng-value с фильтром.

например:

ng-value="price | number:8"

4

Используйте директиву, которая добавляет в коллекции $ formatters и $ parsers, чтобы гарантировать, что преобразование выполняется в обоих направлениях.

См. Этот другой ответ для получения дополнительных сведений, включая ссылку на jsfiddle.


3

У меня была аналогичная проблема, и я использовал

ng-change="handler(objectInScope)" 

в моем обработчике я вызываю метод objectInScope для правильного изменения (грубый ввод). В контроллере я где-то инициировал, что

$scope.objectInScope = myObject; 

Я знаю, что здесь не используются какие-либо причудливые фильтры или наблюдатели ... но это просто и отлично работает. Единственным недостатком этого является то, что objectInScope отправляется в вызове обработчику ...


1

Если вы выполняете сложную асинхронную проверку ввода, возможно, стоит абстрагироваться ng-modelна один уровень выше как часть настраиваемого класса с его собственными методами проверки.

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>

код

(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) })
  };

})();

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.