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
ngModel
lui-même
- Une coutume
ControlValueAccessor
qui 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 TagsComponent
composant définit la logique pour ajouter et supprimer des éléments dans la tags
liste.
@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 setValue
seul (le nom n'est pas important ici). Nous l'utilisons plus tard pour fournir la valeur du ngModel
au 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' ControlValueAccessor
interface. Un fournisseur doit être défini pour cet accesseur de valeur par rapport au NG_VALUE_ACCESSOR
jeton (n'oubliez pas de l'utiliser forwardRef
car 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 onChange
mé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 writeValue
est appelé lorsque la valeur liée dans le ngForm
est mise à jour. Après avoir injecté le composant attaché (ie TagsComponent), nous pourrons l'appeler pour passer cette valeur (voir la setValue
méthode précédente ).
N'oubliez pas de fournir le CUSTOM_VALUE_ACCESSOR
dans 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 tags
de l'entreprise, l' valid
attribut du companyForm.controls.tags
contrôle devient false
automatiquement.
Consultez cet article (section "Composant compatible NgModel") pour plus de détails: