In effetti, ci sono due cose da implementare:
- Un componente che fornisce la logica del componente del modulo. Non è un input poiché verrà fornito da
ngModelsolo
- Una consuetudine
ControlValueAccessorche implementerà il ponte tra questo componente e ngModel/ngControl
Facciamo un campione. Voglio implementare un componente che gestisce un elenco di tag per un'azienda. Il componente consentirà di aggiungere e rimuovere tag. Voglio aggiungere una convalida per assicurarmi che l'elenco dei tag non sia vuoto. Lo definirò nel mio componente come descritto di seguito:
(...)
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]
});
}
}
Il TagsComponentcomponente definisce la logica per aggiungere e rimuovere elementi tagsnell'elenco.
@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 = '';
}
}
Come puoi vedere, non c'è alcun input in questo componente ma setValueuno (il nome non è importante qui). Lo usiamo in seguito per fornire il valore dal ngModelal componente. Questo componente definisce un evento per notificare quando lo stato del componente (l'elenco dei tag) viene aggiornato.
Implementiamo ora il collegamento tra questo componente e ngModel/ ngControl. Ciò corrisponde a una direttiva che implementa l' ControlValueAccessorinterfaccia. È necessario definire un provider per questa funzione di accesso del valore rispetto al NG_VALUE_ACCESSORtoken (non dimenticare di utilizzare forwardRefpoiché la direttiva è definita dopo).
La direttiva collegherà un listener di eventi tagsChangeall'evento dell'host (cioè il componente su cui è attaccata la direttiva, cioè il TagsComponent). Il onChangemetodo verrà chiamato quando si verifica l'evento. Questo metodo corrisponde a quello registrato da Angular2. In questo modo sarà a conoscenza delle modifiche e aggiornerà di conseguenza il controllo del modulo associato.
La writeValuesi chiama quando il valore legato alla ngFormviene aggiornato. Dopo aver iniettato il componente collegato su (ie TagsComponent), potremo chiamarlo per passare questo valore (vedi il setValuemetodo precedente ).
Non dimenticare di fornire il CUSTOM_VALUE_ACCESSORnei collegamenti della direttiva.
Ecco il codice completo della 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; }
}
In questo modo quando rimuovo tutti i dati tagsdell'azienda, l' validattributo del companyForm.controls.tagscontrollo diventa falseautomaticamente.
Consulta questo articolo (sezione "Componente compatibile con NgModel") per maggiori dettagli: