Как я могу переопределить Angular фильтрацию недопустимых значений формы, заставляя Angular сохранять $viewValue в $modelValue?

Мне нужно иметь возможность временно сохранять данные, которые еще не полностью проверены, а затем принудительно выполнить проверку, когда я готов сделать ее постоянной. Но Angular предотвращает это.

У меня есть форма. Пользователь может saveDraft() на ранних версиях формы, которые сохраняются на сервере. Затем, когда пользователь готов, они могут submit() форму, которая будет сохраняться с разными флагами, тем самым начнув фактическую обработку этих данных.

Проблема, с которой я столкнулся, - это встроенные проверки Angular. Когда пользователь вводит некоторые данные во входные данные с проверками на нем, эти данные кэшируются в свойстве $viewValue. Но если проверка не удалась, она никогда не копировалась в свойство $modelValue, которое является ссылкой на фактическое свойство $scope, которое я связал с входом. И, следовательно, значение "недопустимое" не будет сохранено.

Но нам нужно упорствовать. Мы будем иметь дело с принуждением пользователя к исправлению ошибок проверки позже, когда они отправят(). Кроме того, у нас нет способа узнать, будет ли пользователь отправляться в saveDraft() или submit() на конкретный экземпляр представления/контроллера, поэтому мы не можем предварительно настроить представления и проверку по-разному заранее.

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

Я пробовал настройку $scope.formName.inputName.$modelValue = $scope.formName.inputName.$viewValue, но это, похоже, просто расстроило богов, так как оба значения затем нуль.

Я пробовал использовать $scope.formName.inputName.$setValidity('', true), но это только обновляет состояние пользовательского интерфейса. Он никогда не касается $modelValue.

Я могу с успехом использовать $scope.model.boundPropertyName = $scope.formName.inputName.$viewValue, но это очень важно и не допускает никакой разницы между идентификаторами для boundPropertyName и inputName. Другими словами, вам либо нужны индивидуальные функции для всех элементов управления формами, либо вам нужно полагаться на соглашения об именах. Оба они супер-хрупкие.

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

Другие варианты, которые я рассматриваю, но не довольны:

  • Запустить проверку вручную, только когда пользователь нажимает submit(). Но это поражает значение UX мгновенной встроенной проверки в пользовательском интерфейсе. Мы могли бы просто выгрузить всю валидацию на сервер и совершать туда-обратно каждый раз.
  • Сделайте копии ngModel и ngModelController и обезьяны-патчи, чтобы обновить $modelValue независимо от действительности. Но это взламывает рамки, когда должен быть более элегантный путь.

Смотрите здесь CodePen.

(Боковое примечание: Angular, по-видимому, фильтрует данные в соответствии с валидатором в обоих направлениях. Если вы установите значение по умолчанию для модели для formData.zip из '1234', что недостаточно для символов validate, Angular даже не показывает его. Он никогда не достигает начального $viewValue.)

Ответ 1

Следующее решение может использоваться с Angular версии 1.3:

Вы можете установить ng-model-options="{ allowInvalid: true }" в поле ввода модели, где вы хотите сохранить недопустимые атрибуты.

allowInvalid: логическое значение, указывающее, что модель может быть установлена со значениями, которые не корректно проверялись, а не по умолчанию поведение установки модели на undefined

https://docs.angularjs.org/api/ng/directive/ngModelOptions

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

например. if($scope.myFormName.my_input_name.$invalid) { ... }

Соответствующий учебник: http://blog.thoughtram.io/angularjs/2014/10/19/exploring-angular-1.3-ng-model-options.html

Ответ 2

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

Как он указывает, массив $parsers из NgModelController:

Массив функций для выполнения, как конвейер, всякий раз, когда элемент управления считывает значение из DOM. Каждая функция называется, в свою очередь, передавая значение до следующего. Используется для дезинфекции/преобразования значения, а также для проверки. Для проверки синтаксические анализаторы должны обновить состояние действительности с помощью $setValidity() и вернуть undefined для недопустимых значений.

Итак, чтобы модель была обновлена ​​до недопустимого значения, но сохраните результаты проверки, создайте директиву, которая не возвращает undefined для недопустимых значений. Например, директива Emil возвращает недействительные строковые значения undefined в значение модели, в противном случае оно возвращает значение вида:

angular.module('jdFixInvalidValueFormatting', [])
.directive('input', function() {
    return {
        require: '?ngModel',
        restrict: 'E',
        link: function($scope, $element, $attrs, ngModelController) {
            var inputType = angular.lowercase($attrs.type);

            if (!ngModelController || inputType === 'radio' ||
                    inputType === 'checkbox') {
                return;
            }

            ngModelController.$formatters.unshift(function(value) {
                if (ngModelController.$invalid && angular.isUndefined(value)
                        && typeof ngModelController.$modelValue === 'string') {
                    return ngModelController.$modelValue;
                } else {
                    return value;
                }
           });
       }
    };
});

Вы можете видеть, как он работает в своем Plunker (также обратите внимание на его улучшенную обработку null, а не undefined): http://plnkr.co/edit/gist:6674554?p=preview

Ответ 3

Я сделал удар, чтобы сделать это. Основная идея заключается в том, чтобы обновить модель независимо от того, действителен ли вход, захватывая текст сразу с элемента ввода. Затем, обновив представление $render с данными модели, даже если представление undefined. Я не получил это, чтобы изменить стиль при загрузке с плохими данными. Я уверен, что это просто вызов $setViewValue() с чем-то недопустимым, а затем обновление элемента.

Это не ужасно angular способ сделать что-то. Если вам нужна неверная версия формы, я мог бы использовать директиву для соединения think my-model-no-validation="myDirtyModel.value", но это задача для другого дня.

Вот скрипка: http://jsfiddle.net/fooby12/dqPbz/

Сокращенный код для директивы:

angular.module('bindApp', []).
controller('bindToViewCtrl', function($scope) {
    $scope.zip = /(^\d{5}$)|(^\d{5}-\d{4}$)/;
    $scope.formData = {
        zip: '1234',
        bindToView: true
    };
}).
directive('bindToView', function($log) {
    return {
        require: 'ngModel',
        scope: {bindToViewIsOn: '&bindToView', ngModel: '='},
        link: function (scope, iElem, iAttr, ngModel) {
            if(!ngModel) return;    

            ngModel.$render = function() {
                iElem[0].value = ngModel.$viewValue || ngModel.$modelValue;  
            };

            iElem.on('blur keyup change', function() {
                scope.$apply(updateModel);
            });

            scope.$watch(function() { return scope.bindToViewIsOn(); }, function() {
                updateModel();
            });

            function updateModel(){
                if(scope.bindToViewIsOn()){  
                    scope.ngModel = iElem[0].value;
                }
            }
        }
    };
});

Пример HTML:

<div ng-app="bindApp" ng-controller="bindToViewCtrl">
    <form name="bindForm">
        <label> Zip Code
            <input type="text" ng-pattern="zip" required ng-model="formData.zip" bind-to-view="formData.bindToView" name="zipCode"/>
        </label>
        <span>$scope.formData.zip: {{formData.zip}}</span>
        <br/>
        <button ng-click="formData.bindToView = !formData.bindToView">
            Bind to View is {{formData.bindToView ? 'On' : 'Off' }}
        </button> 
    </form>
</div>

Ответ 4

убедитесь, что вы вводите $scope в эту инициализацию контроллера, потому что у меня возникла такая же проблема с автозавершением typeahead, я исправляю эту проблему, установив правильность сохранения, как показано ниже:

 if (!self.editForm.$valid && self.editForm.txtCustomer.$invalid) {//workaround to fix typeahead validation issue.
                self.editForm.txtCustomer.$setValidity('editable', true);
            }