Как я могу переопределить 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);
}