从指令内部 $watch 对 ngModel 的外部更改的正确方法
Proper way to $watch external changes to a ngModel from inside a directive
我正在尝试制定一个从两个输入源读取的指令,然后做一些事情将其变成一个。为此,我正在监听两个输入的变化,并将新的组合值分配给指令的 ngModel。
问题是我还需要知道何时在指令外修改 ngModel 以执行反向过程并正确设置我的两个指令输入源的值。有正确的方法吗?
我做了一个片段来更好地说明问题,这不是我的实际指令。
angular.module('myapp', [])
.controller('AppController', function($scope, $timeout) {
$scope.data = {
value: ''
};
$timeout(function() {
$scope.data.value = 'hellooooo';
}, 5000);
})
.directive('sampleDirective', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
scope: {
ngModel: '='
},
controller: function($scope) {
$scope.data = {};
$scope.changed = function() {
$scope.ngModel = $scope.data.input1 + ' + ' + $scope.data.input2;
}
// The watch is running even when the ngModel is modified inside the changed function above
// I want it to only run when the model is changed from outside the directive, like
// I'm doing in the AppController.
$scope.$watch('ngModel', function(value) {
if (value) {
// Just simulating some processing
var length = value.length;
$scope.data.input1 = value.slice(0, length/2);
$scope.data.input2 = value.slice(length/2, length-1);
}
});
}
};
});
<div ng-app="myapp">
<div ng-controller="AppController">
<sample-directive ng-model="data.value"></sample-directive>
<br>
<br>
<div>{{ data.value }}</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
相当奇怪的要求,但您可以更改指令以使用 ngModelController 代替
.directive('sampleDirective', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
scope: {
ngModel: '='
},
link: function(scope, element, attributes, ngModelCtrl) {
// This will be called when it is changed from outside the directive
ngModelCtrl.$formatters.push(function(value) {
if (value) {
// Just simulating some processing
var length = value.length;
scope.data.input1 = value.slice(0, length/2);
scope.data.input2 = value.slice(length/2, length-1);
}
return value;
});
scope.changed = function() {
// Set the model value from inside the directive
ngModelCtrl.$setViewValue(scope.data.input1 + ' + ' + scope.data.input2);
}
},
controller: function($scope) {
$scope.data = {};
}
};
})
您拥有的是自定义输入控件。它是一个具有 2 个文本框的输入控件,但 "model" 实际上是两者的组合。
要实现 "supports" ngModel
的自定义输入控件,即与 require: ngModel
的其他指令集成,您的指令还需要 require: ngModel
并使用 ngModelController
要集成的挂钩。
所以,不要只做 scope: { ngModel: "=" }
。这样,您就可以双向绑定到附加到 ng-model
属性的模型,但是当您使用 scope.ngModel = "something"
.
设置此值时,事情不会像您期望的那样工作
Angular 提供了一个 example of a custom input control in its documentation of ngModelController
。简而言之,您需要协调设置 $viewValue
- 当基础视图发生变化时,并设置视图 - 当模型发生变化时。
.directive("sampleDirective", function(){
return {
require: "ngModel",
scope: true,
template: '<input ng-model="d.input1" ng-change="viewChanged()">\
<input ng-model="d.input2" ng-change="viewChanged()">',
link: function(scope, element, attrs, ngModel){
var d = scope.d = {};
ngModel.$render = render;
scope.viewChanged = read;
// defines how to render based on model changes
function render(){
var modelValue = ngModel.$modelValue || "";
var length = modelValue.length;
d.input1 = modelValue.slice(0, length/2);
d.input2 = length > 1 ? modelValue.slice(length/2, length) : "";
};
// defines how to set the model based on DOM changes
function read(){
var newViewValue = d.input1 + d.input2;
ngModel.$setViewValue(newViewValue);
}
}
};
});
然后,您的控件将与任何其他支持 ngModel
的指令一起工作,例如 required
或 ng-pattern
或 ng-change
,并且可以参与表单验证:
<div ng-form="form1">
<sample-directive ng-model="foo" maxlength="8" ng-change="doSomething()">
</sample-directive>
</div>
我正在尝试制定一个从两个输入源读取的指令,然后做一些事情将其变成一个。为此,我正在监听两个输入的变化,并将新的组合值分配给指令的 ngModel。
问题是我还需要知道何时在指令外修改 ngModel 以执行反向过程并正确设置我的两个指令输入源的值。有正确的方法吗?
我做了一个片段来更好地说明问题,这不是我的实际指令。
angular.module('myapp', [])
.controller('AppController', function($scope, $timeout) {
$scope.data = {
value: ''
};
$timeout(function() {
$scope.data.value = 'hellooooo';
}, 5000);
})
.directive('sampleDirective', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
scope: {
ngModel: '='
},
controller: function($scope) {
$scope.data = {};
$scope.changed = function() {
$scope.ngModel = $scope.data.input1 + ' + ' + $scope.data.input2;
}
// The watch is running even when the ngModel is modified inside the changed function above
// I want it to only run when the model is changed from outside the directive, like
// I'm doing in the AppController.
$scope.$watch('ngModel', function(value) {
if (value) {
// Just simulating some processing
var length = value.length;
$scope.data.input1 = value.slice(0, length/2);
$scope.data.input2 = value.slice(length/2, length-1);
}
});
}
};
});
<div ng-app="myapp">
<div ng-controller="AppController">
<sample-directive ng-model="data.value"></sample-directive>
<br>
<br>
<div>{{ data.value }}</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.3/angular.js"></script>
相当奇怪的要求,但您可以更改指令以使用 ngModelController 代替
.directive('sampleDirective', function() {
return {
require: 'ngModel',
restrict: 'E',
template: '<input ng-model="data.input1" ng-change="changed()"><input ng-model="data.input2" ng-change="changed()">',
scope: {
ngModel: '='
},
link: function(scope, element, attributes, ngModelCtrl) {
// This will be called when it is changed from outside the directive
ngModelCtrl.$formatters.push(function(value) {
if (value) {
// Just simulating some processing
var length = value.length;
scope.data.input1 = value.slice(0, length/2);
scope.data.input2 = value.slice(length/2, length-1);
}
return value;
});
scope.changed = function() {
// Set the model value from inside the directive
ngModelCtrl.$setViewValue(scope.data.input1 + ' + ' + scope.data.input2);
}
},
controller: function($scope) {
$scope.data = {};
}
};
})
您拥有的是自定义输入控件。它是一个具有 2 个文本框的输入控件,但 "model" 实际上是两者的组合。
要实现 "supports" ngModel
的自定义输入控件,即与 require: ngModel
的其他指令集成,您的指令还需要 require: ngModel
并使用 ngModelController
要集成的挂钩。
所以,不要只做 scope: { ngModel: "=" }
。这样,您就可以双向绑定到附加到 ng-model
属性的模型,但是当您使用 scope.ngModel = "something"
.
Angular 提供了一个 example of a custom input control in its documentation of ngModelController
。简而言之,您需要协调设置 $viewValue
- 当基础视图发生变化时,并设置视图 - 当模型发生变化时。
.directive("sampleDirective", function(){
return {
require: "ngModel",
scope: true,
template: '<input ng-model="d.input1" ng-change="viewChanged()">\
<input ng-model="d.input2" ng-change="viewChanged()">',
link: function(scope, element, attrs, ngModel){
var d = scope.d = {};
ngModel.$render = render;
scope.viewChanged = read;
// defines how to render based on model changes
function render(){
var modelValue = ngModel.$modelValue || "";
var length = modelValue.length;
d.input1 = modelValue.slice(0, length/2);
d.input2 = length > 1 ? modelValue.slice(length/2, length) : "";
};
// defines how to set the model based on DOM changes
function read(){
var newViewValue = d.input1 + d.input2;
ngModel.$setViewValue(newViewValue);
}
}
};
});
然后,您的控件将与任何其他支持 ngModel
的指令一起工作,例如 required
或 ng-pattern
或 ng-change
,并且可以参与表单验证:
<div ng-form="form1">
<sample-directive ng-model="foo" maxlength="8" ng-change="doSomething()">
</sample-directive>
</div>