Suche…


Eine einfache Kontrolle: Bewertung

Lassen Sie uns ein einfaches Steuerelement, ein Bewertungs-Widget, erstellen, das wie folgt verwendet werden soll:

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

Kein fancy CSS für jetzt; das würde als:

0 1 2 3 4 5 x

Durch Klicken auf eine Zahl wird diese Bewertung ausgewählt. Durch Klicken auf "x" wird die Bewertung auf null gesetzt.

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

Schlüsselpunkte:

  1. Implementieren Sie ngModel.$render , um den Ansichtswert des Modells in Ihre Ansicht zu übertragen.
  2. Rufen Sie ngModel.$setViewValue() wenn Sie der Ansicht sind, dass der Ansichtswert aktualisiert werden soll.
  3. Die Steuerung kann natürlich parametriert werden; Verwenden Sie '<' Gültigkeitsbereichsbindungen für Parameter, wenn in Angular> = 1,5, um die Eingabe eindeutig anzugeben. Wenn Sie Maßnahmen ergreifen müssen, wenn sich ein Parameter ändert, können Sie eine JavaScript-Eigenschaft verwenden (siehe Object.defineProperty() ), um einige Uhren zu speichern.

Hinweis 1: Um die Implementierung nicht zu ctrl.options , werden die Bewertungswerte in ein Array eingefügt - die ctrl.options . Dies ist nicht erforderlich. Eine effizientere, aber auch komplexere Implementierung könnte die DOM-Manipulation zum Einfügen / Entfernen von Bewertungen verwenden, wenn sich die min / max Änderung ändert.

Hinweis 2: Mit Ausnahme der Gültigkeitsbereichsbindungen '<' kann dieses Beispiel in Angular <1.5 verwendet werden. Wenn Sie sich auf Angular> = 1.5 befinden, sollten Sie dies in eine Komponente umwandeln und den Lebenszyklus-Hook $onInit() , um min und max zu initialisieren, anstatt dies im Konstruktor des Controllers zu tun.

Und eine notwendige Geige: https://jsfiddle.net/h81mgxma/

Einige komplexe Steuerelemente: Bearbeiten Sie ein vollständiges Objekt

Eine benutzerdefinierte Steuerung muss sich nicht auf banale Dinge wie Primitive beschränken. Es kann interessantere Dinge bearbeiten. Hier stellen wir zwei Arten von benutzerdefinierten Steuerelementen vor, eines zum Bearbeiten von Personen und eines zum Bearbeiten von Adressen. Die Adresssteuerung wird verwendet, um die Adresse der Person zu bearbeiten. Ein Beispiel für die Verwendung wäre:

<input-person ng-model="data.thePerson"></input-person>
<input-address ng-model="data.thePerson.address"></input-address>

Das Modell für dieses Beispiel ist bewusst vereinfachend:

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

Der Adresseditor:

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

Schlüsselpunkte:

  1. Wir bearbeiten ein Objekt. Wir möchten das Objekt, das wir von unseren Eltern erhalten haben, nicht direkt ändern (wir möchten, dass unser Modell mit dem Unveränderlichkeitsprinzip kompatibel ist). Daher erstellen wir eine flache $setViewValue() für das zu bearbeitende Objekt und aktualisieren das Modell mit $setViewValue() wenn sich eine Eigenschaft ändert. Wir geben eine Kopie an unsere Eltern weiter.
  2. Wenn das Modell von außen geändert wird, kopieren wir es und speichern die Kopie in unserem Gültigkeitsbereich. Unveränderlichkeitsprinzipien wieder, obwohl die interne Kopie nicht unveränderlich ist, könnte die externe Kopie sehr wohl sein. Außerdem bauen wir die Uhr neu auf ( this_unwatch();this._makeWatch(); ), um zu vermeiden, dass der Beobachter für Änderungen ausgelöst wird, die vom Modell an uns this_unwatch();this._makeWatch(); . (Wir möchten nur, dass die Uhr für Änderungen an der Benutzeroberfläche ausgelöst wird.)
  3. ngModel.$render() den oben genannten Punkten implementieren wir ngModel.$render() und rufen ngModel.$setViewValue() wie ein einfaches Steuerelement (siehe Bewertungsbeispiel).

Der Code für die Personensteuerungsfunktion ist nahezu identisch. Die Vorlage verwendet die <input-address> . In einer fortgeschritteneren Implementierung könnten wir die Controller in einem wiederverwendbaren Modul extrahieren.

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

Hinweis: Hier werden die Objekte typisiert, dh sie verfügen über korrekte Konstruktoren. Dies ist nicht obligatorisch. Das Modell kann ein einfaches JSON-Objekt sein. Verwenden angular.copy() in diesem Fall einfach angular.copy() anstelle der Konstruktoren. Ein zusätzlicher Vorteil ist, dass der Controller für die beiden Steuerungen identisch ist und leicht in ein gemeinsames Modul extrahiert werden kann.

Die Geige: https://jsfiddle.net/3tzyqfko/2/

Zwei Versionen der Geige haben den allgemeinen Code der Controller extrahiert: https://jsfiddle.net/agj4cp0e/ und https://jsfiddle.net/ugb6Lw8b/



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow