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
ngModel
solo
- Una consuetudine
ControlValueAccessor
che 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 TagsComponent
componente definisce la logica per aggiungere e rimuovere elementi tags
nell'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 setValue
uno (il nome non è importante qui). Lo usiamo in seguito per fornire il valore dal ngModel
al 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' ControlValueAccessor
interfaccia. È necessario definire un provider per questa funzione di accesso del valore rispetto al NG_VALUE_ACCESSOR
token (non dimenticare di utilizzare forwardRef
poiché la direttiva è definita dopo).
La direttiva collegherà un listener di eventi tagsChange
all'evento dell'host (cioè il componente su cui è attaccata la direttiva, cioè il TagsComponent
). Il onChange
metodo 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 writeValue
si chiama quando il valore legato alla ngForm
viene aggiornato. Dopo aver iniettato il componente collegato su (ie TagsComponent), potremo chiamarlo per passare questo valore (vedi il setValue
metodo precedente ).
Non dimenticare di fornire il CUSTOM_VALUE_ACCESSOR
nei 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 tags
dell'azienda, l' valid
attributo del companyForm.controls.tags
controllo diventa false
automaticamente.
Consulta questo articolo (sezione "Componente compatibile con NgModel") per maggiori dettagli: