De hecho, hay dos cosas que implementar:
- Un componente que proporciona la lógica de su componente de formulario. No necesita una entrada ya que se proporcionará por
ngModelsí mismo
- Una costumbre
ControlValueAccessorque implementará el puente entre este componente y ngModel/ngControl
Tomemos una muestra. Quiero implementar un componente que gestione una lista de etiquetas para una empresa. El componente permitirá agregar y eliminar etiquetas. Quiero agregar una validación para asegurarme de que la lista de etiquetas no esté vacía. Lo definiré en mi componente como se describe a continuación:
(...)
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]
});
}
}
El TagsComponentcomponente define la lógica para agregar y eliminar elementos en la tagslista.
@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 = '';
}
}
Como puede ver, no hay ninguna entrada en este componente, sino una setValue(el nombre no es importante aquí). Lo usamos más tarde para proporcionar el valor del ngModelal componente. Este componente define un evento para notificar cuando se actualiza el estado del componente (la lista de etiquetas).
Implementemos ahora el vínculo entre este componente y ngModel/ ngControl. Esto corresponde a una directiva que implementa la ControlValueAccessorinterfaz. Se debe definir un proveedor para este descriptor de acceso de valor contra el NG_VALUE_ACCESSORtoken (no olvide usarlo forwardRefya que la directiva se define después).
La directiva adjuntará un detector de tagsChangeeventos al evento del host (es decir, el componente al que se adjunta la directiva, es decir, el TagsComponent). El onChangemétodo será llamado cuando ocurra el evento. Este método corresponde al registrado por Angular2. De esta manera, estará al tanto de los cambios y actualizará el control de formulario asociado.
Se writeValueinvoca cuando ngFormse actualiza el valor enlazado en . Después de haber inyectado el componente adjunto (es decir, TagsComponent), podremos llamarlo para pasar este valor (ver el setValuemétodo anterior ).
No olvide proporcionar CUSTOM_VALUE_ACCESSORen los enlaces de la directiva.
Aquí está el código completo de la costumbre 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 esta manera cuando elimino todos los tagsde la empresa, el validatributo del companyForm.controls.tagscontrol se convierte falseautomáticamente.
Consulte este artículo (sección "Componente compatible con NgModel") para obtener más detalles: