Angularjs меняет ng-модель в директиве на тот же элемент с проверками

В самом примитивном угловом приложении я пытаюсь создать директиву для поля ввода, которое изменяет значение родительской ng-модели.

HTML:

<form novalidate>
  <input ng-model="ctrl.myvalue" mydirective minlength="19" />

  {{ ctrl.myvalue }}
</form>

JS:

var app = angular.module('app', []);

app.directive('mydirective', function(){
    return {
        scope: { ngModel: '=' },
        link: function(scope, el) {
           el.on('input', function(e) {
              this.value = this.value.replace(/ /g,'');

              scope.ngModel = this.value;
           })
        }
    }
})

app.controller('MyController', function(){
  this.myvalue = '';
})

Планкер

Проблема в том, что если я использую эту директиву вместе с minlength или pattern для проверки ввода, она получает определенное поведение: каждая вторая буква, которую вы вводите во вводе, исчезает; также ng-model получает значение undefined. Без валидации код работает отлично.

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

Не могли бы вы объяснить это или предложить путь?


person smnbbrv    schedule 11.08.2015    source источник
comment
Вы действительно должны привязать данные к чему-то другому, кроме атрибута ngModel, это сбивает с толку.   -  person Robin Jonsson    schedule 11.08.2015


Ответы (2)


Вы можете использовать unsift, а также визуализировать для первой итерации. Обычно вы можете использовать ctrl.$setViewValue, но вы можете быть уверены, что перезапуск не произойдет, если значение не изменится...

var testModule = angular.module('myModule', []);

testModule.controller('testCntrl', ['$scope', function ($scope) {
    $scope.test = 'sdfsd  fsdf sdfsd sdf';

}]);

testModule.directive('cleanSpaces', [function () {
    return {
        require: '?ngModel',
        link: function (scope, $elem, attrs, ctrl) {
            if (!ctrl) return;

            var filterSpaces = function (str) {
                return str.replace(/ /g, '');
            }

            ctrl.$parsers.unshift(function (viewValue) {

                var elem = $elem[0],
                    pos = elem.selectionStart,
                    value = '';

                if (pos !== viewValue.length) {
                    var valueInit = filterSpaces(
                    viewValue.substring(0, elem.selectionStart));
                    pos = valueInit.length;
                }

                //I launch the regular expression, 
                // maybe you prefer parse the rest 
                // of the substring and concat.

                value = filterSpaces(viewValue);
                $elem.val(value);

                elem.setSelectionRange(pos, pos);

                ctrl.$setViewValue(value);

                return value;
            });

            ctrl.$render = function () {
                if (ctrl.$viewValue) {
                    ctrl.$setViewValue(filterSpaces(ctrl.$viewValue));
                }
            };
        }
    };
}]);

http://jsfiddle.net/luarmr/m4dmz0tn/

ОБНОВЛЕНИЕ Я обновляю скрипт последним кодом и примером проверки в angular и обновляю html с помощью ng-trim (ngModel.$parsers ingore пробел в конце значения ng-model).

person Raúl Martín    schedule 11.08.2015
comment
Спасибо за решение. То, с чем я сейчас сталкиваюсь в вашей скрипке, не фильтрует пробелы в конце ввода; также у него такое же забавное поведение, когда я пытаюсь вставить пробел внутри ввода. Первое место съедено, а второе напечатано - person smnbbrv; 13.08.2015
comment
второй пробел не съеден.. не могу воспроизвести. Последнее пространство, это странное поведение в angular. Вам нужно добавить ng-trim. Я обновляю скрипач. Не забудьте обновить свою версию angular. - person Raúl Martín; 13.08.2015
comment
@simon Я думаю, что обе проблемы решены. можешь посмотреть. Обновляю модель в парсере ctrl.$setViewValue(value);. Я боялся, что обновление модели накладывает ограничение на другую директиву, но я добавляю еще одну директиву remove-letter-f и работает отлично - person Raúl Martín; 13.08.2015

Используйте Angular NgModelController. Я просто добавляю в $parsers (функции, которые выполняются при обновлении представления, но до того, как значение будет сохранено в модели). Здесь я помещаю функцию в конвейер $parsers. Имейте в виду, что модель не будет заполнена до тех пор, пока не будет выполнена проверка минимальной длины. Фрагмент кода показывает как $viewValue, так и modelValue.

var app = angular.module('app', []);

app.directive('mydirective', function() {
  return {
    require: 'ngModel',
    priority: 100,
    link: function(scope, el, attrs, ngModelCtrl) {
      // $parsers from view/DOM to model
      ngModelCtrl.$parsers.push(function(value) {
        console.log(value);
        return value && value.replace(/ /g, '');
      });
    }
  }
})

app.controller('MyController', function() {
  this.myvalue = '';
})
<script src="https://code.angularjs.org/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="MyController as ctrl">
  <form name="myForm" novalidate>
    <input ng-model="ctrl.myvalue" name="myValue" mydirective minlength="19" /><br /><br />Model Value: {{ ctrl.myvalue }}<br /><br />
    View Value: {{ myForm.myValue.$viewValue }}
  </form>
</div>

Обновление: если вы пытаетесь выполнить пользовательскую проверку, просто забудьте о параметрах minlength/required и просто напишите свой собственный. Вероятно, это не самое приятное поведение - изменять текст по мере ввода пользователем. В этом примере в viewValue будут добавлены пробелы в событии размытия. Я все еще думаю, что ngModelController — это то, что нужно, но я недостаточно знаю, чего вы пытаетесь достичь, чтобы дать вам что-то более близкое к тому, что вы ищете.

var app = angular.module('app', []);

app.directive('creditCardValidator', function() {
  return {
    require: 'ngModel',
    priority: 100,
    link: function(scope, el, attrs, ngModelCtrl) {
      // 16 characters
      attrs.$set('maxlength', 16);

      var noSpaces = function noSpaces(value) {
        return value.replace(/ /g, '');
      }
      var withSpaces = function withSpaces(value) {
        if (ngModelCtrl.$isEmpty(value)) {
          return;
        }

        var spacedValue = value.replace(/(\d{4})(\d{4})(\d{4})(\d{4})/, '$1 $2 $3 $4');
        return spacedValue || undefined;
      }

      ngModelCtrl.$parsers.push(noSpaces);
      ngModelCtrl.$formatters.push(withSpaces);

      ngModelCtrl.$validators.validCreditCard = function(modelValue, viewValue) {
        var value = noSpaces(modelValue || viewValue);
        var valid = /^\d{16}$/.test(value);
        return valid;
      };

      el.on('blur', function() {
        if (ngModelCtrl.$valid) {
          ngModelCtrl.$setViewValue(withSpaces(ngModelCtrl.$modelValue));
          ngModelCtrl.$render();
        }
      });
    }
  }
})

app.controller('MyController', function() {
  this.myvalue = '';
})
<script src="https://code.angularjs.org/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-controller="MyController as ctrl">
  <form name="myForm" novalidate>
    <input ng-model="ctrl.myvalue" name="myValue" ng-model-options="{allowInvalid: true}" credit-card-validator />
    <br />
    <br />Model Value: {{ ctrl.myvalue }}
    <br />
    <br />View Value: {{ myForm.myValue.$viewValue }}
    <br />
    <br />Error: {{ myForm.myValue.$error }}
  </form>
</div>

person Patrick    schedule 11.08.2015
comment
Привет, Патрик, моя проблема на самом деле не только в передаче значения модулю, но и в изменении значения представления. Более глубоко мне нужно разделить части кредитной карты на 4 части с пробелами между ними. Я уже пробовал решение с парсерами и форматтерами, но их результаты меня не удовлетворили. Это почти работало, но если я изменил число в середине, курсор прыгал в конце ввода. Вот почему я вынужден не использовать парсеры/форматеры. - person smnbbrv; 11.08.2015