Поиск…


Простой контроль: рейтинг

Давайте построим простой элемент управления, виджет рейтинга, предназначенный для использования как:

<rating min="0" max="5" nullifier="true" ng-model="data.rating"></rating>

На данный момент нет модного CSS; это будет выглядеть так:

0 1 2 3 4 5 x

Нажатие на номер выбирает этот рейтинг; и нажатие «x» устанавливает рейтинг равным нулю.

app.directive('rating', function() {

    function RatingController() {
        this._ngModel = null;
        this.rating = null;
        this.options = null;
        this.min = typeof this.min === 'number' ? this.min : 1;
        this.max = typeof this.max === 'number' ? this.max : 5;
    }
    
    RatingController.prototype.setNgModel = function(ngModel) {
        this._ngModel = ngModel;
        
        if( ngModel ) {
            // KEY POINT 1
            ngModel.$render = this._render.bind(this);
        }
    };
    
    RatingController.prototype._render = function() {
        this.rating = this._ngModel.$viewValue != null ? this._ngModel.$viewValue : -Number.MAX_VALUE;
    };
    
    RatingController.prototype._calculateOptions = function() {
        if( this.min == null || this.max == null ) {
            this.options = [];
        }
        else {
            this.options = new Array(this.max - this.min + 1);
            for( var i=0; i < this.options.length; i++ ) {
                this.options[i] = this.min + i;
            }
        }
    };
    
    RatingController.prototype.setValue = function(val) {
        this.rating = val;
        // KEY POINT 2
        this._ngModel.$setViewValue(val);
    };
    
    // KEY POINT 3
    Object.defineProperty(RatingController.prototype, 'min', {
        get: function() {
            return this._min;
        },
        set: function(val) {
            this._min = val;
            this._calculateOptions();
        }
    });
    
    Object.defineProperty(RatingController.prototype, 'max', {
        get: function() {
            return this._max;
        },
        set: function(val) {
            this._max = val;
            this._calculateOptions();
        }
    });
    
    return {
        restrict: 'E',
        scope: {
            // KEY POINT 3
            min: '<?',
            max: '<?',
            nullifier: '<?'
        },
        bindToController: true,
        controllerAs: 'ctrl',
        controller: RatingController,
        require: ['rating', 'ngModel'],
        link: function(scope, elem, attrs, ctrls) {
            ctrls[0].setNgModel(ctrls[1]);
        },
        template:
            '<span ng-repeat="o in ctrl.options" href="#" class="rating-option" ng-class="{\'rating-option-active\': o <= ctrl.rating}" ng-click="ctrl.setValue(o)">{{ o }}</span>' +
            '<span ng-if="ctrl.nullifier" ng-click="ctrl.setValue(null)" class="rating-nullifier">&#10006;</span>'
    };
});

Ключевые моменты:

  1. Реализовать ngModel.$render для передачи значения просмотра модели на ваш взгляд.
  2. Вызовите ngModel.$setViewValue() когда вы чувствуете, что значение представления должно быть обновлено.
  3. Разумеется, управление может быть параметризовано; используйте привязки '<' для параметров, если в Angular> = 1.5 четко указать ввод - одностороннее связывание. Если вам нужно предпринимать действия всякий раз, когда изменяется параметр, вы можете использовать свойство JavaScript (см. Object.defineProperty() ), чтобы сохранить несколько часов.

Примечание 1: Чтобы не усложнять реализацию, значения рейтинга вставляются в массив - ctrl.options . Это не нужно; более эффективная, но также более сложная реализация может использовать DOM-манипуляцию для вставки / удаления рейтингов при изменении min / max .

Примечание 2: За исключением привязок '<' scope, этот пример можно использовать в Angular <1.5. Если вы находитесь на Angular> = 1.5, было бы неплохо преобразовать это в компонент и использовать крюк жизненного цикла $onInit() для инициализации min и max вместо этого в конструкторе контроллера.

И необходимая скрипка: https://jsfiddle.net/h81mgxma/

Несколько сложных элементов управления: отредактируйте полный объект

Пользовательский элемент управления не должен ограничиваться тривиальными вещами, такими как примитивы; он может редактировать более интересные вещи. Здесь мы представляем два типа пользовательских элементов управления: один для редактирования лиц и один для редактирования адресов. Управление адресом используется для редактирования адреса человека. Примером использования может быть:

<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(); ), чтобы не запускать наблюдателя за изменениями, внесенными нами моделью. (Мы хотим, чтобы часы запускались для изменений, внесенных в пользовательский интерфейс.)
  3. Другое, что выше, мы реализуем ngModel.$render() и вызываем ngModel.$setViewValue() как и для простого 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/



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow