En fait, il y a deux choses à mettre en œuvre:
- Un composant qui fournit la logique de votre composant de formulaire. Ce n'est pas une entrée car il sera fourni par
ngModellui-même
- Une coutume
ControlValueAccessorqui implémentera le pont entre ce composant et ngModel/ngControl
Prenons un échantillon. Je souhaite implémenter un composant qui gère une liste de balises pour une entreprise. Le composant permettra d'ajouter et de supprimer des balises. Je souhaite ajouter une validation pour m'assurer que la liste des balises n'est pas vide. Je vais le définir dans mon composant comme décrit ci-dessous:
(...)
import {TagsComponent} from './app.tags.ngform';
import {TagsValueAccessor} from './app.tags.ngform.accessor';
function notEmpty(control) {
if(control.value == null || control.value.length===0) {
return {
notEmpty: true
}
}
return null;
}
@Component({
selector: 'company-details',
directives: [ FormFieldComponent, TagsComponent, TagsValueAccessor ],
template: `
<form [ngFormModel]="companyForm">
Name: <input [(ngModel)]="company.name"
[ngFormControl]="companyForm.controls.name"/>
Tags: <tags [(ngModel)]="company.tags"
[ngFormControl]="companyForm.controls.tags"></tags>
</form>
`
})
export class DetailsComponent implements OnInit {
constructor(_builder:FormBuilder) {
this.company = new Company('companyid',
'some name', [ 'tag1', 'tag2' ]);
this.companyForm = _builder.group({
name: ['', Validators.required],
tags: ['', notEmpty]
});
}
}
Le TagsComponentcomposant définit la logique pour ajouter et supprimer des éléments dans la tagsliste.
@Component({
selector: 'tags',
template: `
<div *ngIf="tags">
<span *ngFor="#tag of tags" style="font-size:14px"
class="label label-default" (click)="removeTag(tag)">
{{label}} <span class="glyphicon glyphicon-remove"
aria- hidden="true"></span>
</span>
<span> | </span>
<span style="display:inline-block;">
<input [(ngModel)]="tagToAdd"
style="width: 50px; font-size: 14px;" class="custom"/>
<em class="glyphicon glyphicon-ok" aria-hidden="true"
(click)="addTag(tagToAdd)"></em>
</span>
</div>
`
})
export class TagsComponent {
@Output()
tagsChange: EventEmitter;
constructor() {
this.tagsChange = new EventEmitter();
}
setValue(value) {
this.tags = value;
}
removeLabel(tag:string) {
var index = this.tags.indexOf(tag, 0);
if (index != undefined) {
this.tags.splice(index, 1);
this.tagsChange.emit(this.tags);
}
}
addLabel(label:string) {
this.tags.push(this.tagToAdd);
this.tagsChange.emit(this.tags);
this.tagToAdd = '';
}
}
Comme vous pouvez le voir, il n'y a pas d'entrée dans ce composant mais un setValueseul (le nom n'est pas important ici). Nous l'utilisons plus tard pour fournir la valeur du ngModelau composant. Ce composant définit un événement pour notifier lorsque l'état du composant (la liste des balises) est mis à jour.
Implémentons maintenant le lien entre ce composant et ngModel/ ngControl. Cela correspond à une directive qui implémente l' ControlValueAccessorinterface. Un fournisseur doit être défini pour cet accesseur de valeur par rapport au NG_VALUE_ACCESSORjeton (n'oubliez pas de l'utiliser forwardRefcar la directive est définie après).
La directive attachera un écouteur d'événement sur l' tagsChangeévénement de l'hôte (c'est-à-dire le composant auquel la directive est attachée, c'est-à-dire le TagsComponent). La onChangeméthode sera appelée lorsque l'événement se produira. Cette méthode correspond à celle enregistrée par Angular2. De cette façon, il sera informé des modifications et met à jour en conséquence le contrôle de formulaire associé.
Le writeValueest appelé lorsque la valeur liée dans le ngFormest mise à jour. Après avoir injecté le composant attaché (ie TagsComponent), nous pourrons l'appeler pour passer cette valeur (voir la setValueméthode précédente ).
N'oubliez pas de fournir le CUSTOM_VALUE_ACCESSORdans les liaisons de la directive.
Voici le code complet de la custom ControlValueAccessor:
import {TagsComponent} from './app.tags.ngform';
const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));
@Directive({
selector: 'tags',
host: {'(tagsChange)': 'onChange($event)'},
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class TagsValueAccessor implements ControlValueAccessor {
onChange = (_) => {};
onTouched = () => {};
constructor(private host: TagsComponent) { }
writeValue(value: any): void {
this.host.setValue(value);
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}
De cette façon, lorsque je supprime tous les éléments tagsde l'entreprise, l' validattribut du companyForm.controls.tagscontrôle devient falseautomatiquement.
Consultez cet article (section "Composant compatible NgModel") pour plus de détails: