一些複雜的控制元件編輯一個完整的物件
自定義控制元件不必將自己侷限於基元等微不足道的東西; 它可以編輯更多有趣的東西。這裡我們提供兩種型別的自定義控制元件,一種用於編輯人物,另一種用於編輯地址。地址控制元件用於編輯人員的地址。使用的一個例子是:
<input-person ng-model="data.thePerson"></input-person>
<input-address ng-model="data.thePerson.address"></input-address>
這個例子的模型是故意簡化的:
function Person(data) {
data = data || {};
this.name = data.name;
this.address = data.address ? new Address(data.address) : null;
}
function Address(data) {
data = data || {};
this.street = data.street;
this.number = data.number;
}
地址編輯器:
app.directive('inputAddress', function() {
InputAddressController.$inject = ['$scope'];
function InputAddressController($scope) {
this.$scope = $scope;
this._ngModel = null;
this.value = null;
this._unwatch = angular.noop;
}
InputAddressController.prototype.setNgModel = function(ngModel) {
this._ngModel = ngModel;
if( ngModel ) {
// KEY POINT 3
ngModel.$render = this._render.bind(this);
}
};
InputAddressController.prototype._makeWatch = function() {
// KEY POINT 1
this._unwatch = this.$scope.$watchCollection(
(function() {
return this.value;
}).bind(this),
(function(newval, oldval) {
if( newval !== oldval ) { // skip the initial trigger
this._ngModel.$setViewValue(newval !== null ? new Address(newval) : null);
}
}).bind(this)
);
};
InputAddressController.prototype._render = function() {
// KEY POINT 2
this._unwatch();
this.value = this._ngModel.$viewValue ? new Address(this._ngModel.$viewValue) : null;
this._makeWatch();
};
return {
restrict: 'E',
scope: {},
bindToController: true,
controllerAs: 'ctrl',
controller: InputAddressController,
require: ['inputAddress', 'ngModel'],
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setNgModel(ctrls[1]);
},
template:
'<div>' +
'<label><span>Street:</span><input type="text" ng-model="ctrl.value.street" /></label>' +
'<label><span>Number:</span><input type="text" ng-model="ctrl.value.number" /></label>' +
'</div>'
};
});
關鍵點:
- 我們正在編輯一個物件; 我們不想直接改變從父母那裡給我們的物件(我們希望我們的模型與不變性原則相容)。因此,我們在正在編輯的物件上建立一個淺表,並在屬性更改時使用
$setViewValue()
更新模型。我們將副本傳遞給我們的父母。 - 每當模型從外部更改時,我們將其複製並將副本儲存到我們的範圍。不變性原則,雖然內部副本不是一成不變的,但外部可能很好。此外,我們重建手錶(
this_unwatch();this._makeWatch();
),以避免觸發觀察者對模型推送給我們的更改。 (我們只希望手錶觸發 UI 中所做的更改。) - 除了以上幾點,我們實現
ngModel.$render()
並呼叫ngModel.$setViewValue()
,就像我們對一個簡單的控制一樣(參見評級示例)。
人員自定義控制元件的程式碼幾乎相同。模板使用的是 <input-address>
。在更高階的實現中,我們可以在可重用模組中提取控制器。
app.directive('inputPerson', function() {
InputPersonController.$inject = ['$scope'];
function InputPersonController($scope) {
this.$scope = $scope;
this._ngModel = null;
this.value = null;
this._unwatch = angular.noop;
}
InputPersonController.prototype.setNgModel = function(ngModel) {
this._ngModel = ngModel;
if( ngModel ) {
ngModel.$render = this._render.bind(this);
}
};
InputPersonController.prototype._makeWatch = function() {
this._unwatch = this.$scope.$watchCollection(
(function() {
return this.value;
}).bind(this),
(function(newval, oldval) {
if( newval !== oldval ) { // skip the initial trigger
this._ngModel.$setViewValue(newval !== null ? new Person(newval) : null);
}
}).bind(this)
);
};
InputPersonController.prototype._render = function() {
this._unwatch();
this.value = this._ngModel.$viewValue ? new Person(this._ngModel.$viewValue) : null;
this._makeWatch();
};
return {
restrict: 'E',
scope: {},
bindToController: true,
controllerAs: 'ctrl',
controller: InputPersonController,
require: ['inputPerson', 'ngModel'],
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setNgModel(ctrls[1]);
},
template:
'<div>' +
'<label><span>Name:</span><input type="text" ng-model="ctrl.value.name" /></label>' +
'<input-address ng-model="ctrl.value.address"></input-address>' +
'</div>'
};
});
注意:這裡物件是鍵入的,即它們具有適當的建構函式。這不是強制性的; 該模型可以是普通的 JSON 物件。在這種情況下,只需使用 angular.copy()
而不是建構函式。另一個優點是控制器對於兩個控制元件變得相同,並且可以容易地提取到一些通用模組中。
小提琴: https : //jsfiddle.net/3tzyqfko/2/
兩個版本的小提琴已經提取了控制器的通用程式碼: https : //jsfiddle.net/agj4cp0e/ 和 https://jsfiddle.net/ugb6Lw8b/