一些复杂的控件编辑一个完整的对象

自定义控件不必将自己局限于基元等微不足道的东西; 它可以编辑更多有趣的东西。这里我们提供两种类型的自定义控件,一种用于编辑人物,另一种用于编辑地址。地址控件用于编辑人员的地址。使用的一个例子是:

<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/