一些複雜的控制元件編輯一個完整的物件

自定義控制元件不必將自己侷限於基元等微不足道的東西; 它可以編輯更多有趣的東西。這裡我們提供兩種型別的自定義控制元件,一種用於編輯人物,另一種用於編輯地址。地址控制元件用於編輯人員的地址。使用的一個例子是:

<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>'
    };
});

關鍵點:

  1. 我們正在編輯一個物件; 我們不想直接改變從父母那裡給我們的物件(我們希望我們的模型與不變性原則相容)。因此,我們在正在編輯的物件上建立一個淺表,並在屬性更改時使用 $setViewValue() 更新模型。我們將副本傳遞給我們的父母。
  2. 每當模型從外部更改時,我們將其複製並將副本儲存到我們的範圍。不變性原則,雖然內部副本不是一成不變的,但外部可能很好。此外,我們重建手錶(this_unwatch();this._makeWatch();),以避免觸發觀察者對模型推送給我們的更改。 (我們只希望手錶觸發 UI 中所做的更改。)
  3. 除了以上幾點,我們實現 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/