AngularJS
Richtlijnen met ngModelController
Zoeken…
Een eenvoudige bediening: beoordeling
Laten we een eenvoudig besturingselement, een beoordelingswidget, bouwen die bedoeld is om te gebruiken als:
<rating min="0" max="5" nullifier="true" ng-model="data.rating"></rating>
Momenteel geen fancy CSS; dit zou weergeven als:
0 1 2 3 4 5 x
Als u op een cijfer klikt, wordt die beoordeling geselecteerd; en klikken op de "x" stelt de beoordeling in op nul.
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">✖</span>'
};
});
Hoofdpunten:
- Implementeer
ngModel.$render
om de weergavewaarde van het model naar uw weergave over te dragen. - Roep
ngModel.$setViewValue()
wanneer u vindt dat de weergavewaarde moet worden bijgewerkt. - De besturing kan natuurlijk worden geparametreerd; gebruik
'<'
scope bindingen voor parameters, indien in Angular> = 1.5 om invoer duidelijk aan te geven - een manier bindend. Als u actie moet ondernemen telkens wanneer een parameter verandert, kunt u een JavaScript-eigenschap (zieObject.defineProperty()
) gebruiken om een paar horloges op te slaan.
Opmerking 1: Om de implementatie niet te ingewikkeld te maken, worden de beoordelingswaarden in een array ingevoegd - de ctrl.options
. Dit is niet nodig; een efficiëntere, maar ook complexere implementatie zou DOM-manipulatie kunnen gebruiken om beoordelingen in te voegen / te verwijderen bij min
/ max
wijziging.
Opmerking 2: Met uitzondering van de '<'
bindingsbereik '<'
, kan dit voorbeeld worden gebruikt in Angular <1.5. Als u Angular> = 1.5 gebruikt, is het een goed idee om dit naar een component te transformeren en de $onInit()
lifecycle hook te gebruiken om min
en max
te initialiseren, in plaats van dit te doen in de constructor van de controller.
En een noodzakelijke viool: https://jsfiddle.net/h81mgxma/
Een paar complexe bedieningselementen: bewerk een volledig object
Een aangepast besturingselement hoeft zich niet te beperken tot triviale dingen zoals primitieven; het kan interessantere dingen bewerken. Hier presenteren we twee soorten aangepaste besturingselementen, een voor het bewerken van personen en een voor het bewerken van adressen. Het adresbeheer wordt gebruikt om het adres van de persoon te bewerken. Een voorbeeld van gebruik zou zijn:
<input-person ng-model="data.thePerson"></input-person>
<input-address ng-model="data.thePerson.address"></input-address>
Het model voor dit voorbeeld is opzettelijk simplistisch:
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;
}
De adreseditor:
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>'
};
});
Hoofdpunten:
- We zijn een object aan het bewerken; we willen niet direct het object dat ons van onze ouder is gegeven veranderen (we willen dat ons model compatibel is met het onveranderlijkheidsprincipe). Daarom maken we een ondiep horloge van het te bewerken object en werken we het model bij met
$setViewValue()
wanneer een eigenschap verandert. We geven een kopie door aan onze ouder. - Wanneer het model van buitenaf verandert, kopiëren we het en slaan de kopie op in ons bereik. Weer onveranderlijkheidsprincipes, hoewel de interne kopie niet onveranderlijk is, zou de externe dat wel kunnen. Bovendien herbouwen we het horloge (
this_unwatch();this._makeWatch();
)), om te voorkomen dat de watcher wordt geactiveerd voor wijzigingen die ons door het model worden opgedrongen. (We willen alleen dat het horloge wordt geactiveerd voor wijzigingen in de gebruikersinterface.) - Anders dan de bovenstaande punten, implementeren we
ngModel.$render()
en roepen wengModel.$setViewValue()
zoals we zouden doen voor een eenvoudige controle (zie het beoordelingsvoorbeeld).
De code voor het aangepaste beheer van de persoon is bijna identiek. De sjabloon gebruikt het <input-address>
. In een meer geavanceerde implementatie zouden we de controllers in een herbruikbare module kunnen extraheren.
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>'
};
});
Opmerking: hier worden de objecten getypt, dat wil zeggen dat ze de juiste constructors hebben. Dit is niet verplicht; het model kan gewone JSON-objecten zijn. Gebruik in dit geval gewoon angular.copy()
plaats van de constructors. Een bijkomend voordeel is dat de controller identiek wordt voor de twee bedieningselementen en gemakkelijk kan worden uitgepakt in een gemeenschappelijke module.
De viool: https://jsfiddle.net/3tzyqfko/2/
Twee versies van de viool hebben de gemeenschappelijke code van de controllers geëxtraheerd: https://jsfiddle.net/agj4cp0e/ en https://jsfiddle.net/ugb6Lw8b/