Angularjs 在具有验证的同一元素上的指令中更改 ng-model
Angularjs change ng-model within a directive on the same element with validations
在最原始的 angular 应用程序中,我正在尝试为输入字段创建一个指令,该指令会更改父项的 ng-model 值。
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
值。未经验证,代码可以完美运行。
我也尝试创建一个自定义验证作为解决方法,但它具有相同的效果。
你能解释一下或提出前进的方向吗?
使用 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 内容并编写您自己的内容即可。在用户键入时更改文本可能不是最好的行为。此示例将在 blur 事件中将空格放入 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})/, ' ');
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>
您可以使用 unsift,也可以使用 render 进行第一次迭代。
通常你可以使用 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/
UPDATE 我用 angular 中的最后一个代码和验证示例更新了 fiddle,并用 ng-[ 更新了 html =25=] (ngModel.$parsers ingore whitespace at the end of ng-model value).
在最原始的 angular 应用程序中,我正在尝试为输入字段创建一个指令,该指令会更改父项的 ng-model 值。
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
值。未经验证,代码可以完美运行。
我也尝试创建一个自定义验证作为解决方法,但它具有相同的效果。
你能解释一下或提出前进的方向吗?
使用 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 内容并编写您自己的内容即可。在用户键入时更改文本可能不是最好的行为。此示例将在 blur 事件中将空格放入 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})/, ' ');
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>
您可以使用 unsift,也可以使用 render 进行第一次迭代。
通常你可以使用 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/
UPDATE 我用 angular 中的最后一个代码和验证示例更新了 fiddle,并用 ng-[ 更新了 html =25=] (ngModel.$parsers ingore whitespace at the end of ng-model value).