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
ngModel
sí mismo
- Una costumbre
ControlValueAccessor
que 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 TagsComponent
componente define la lógica para agregar y eliminar elementos en la tags
lista.
@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 ngModel
al 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 ControlValueAccessor
interfaz. Se debe definir un proveedor para este descriptor de acceso de valor contra el NG_VALUE_ACCESSOR
token (no olvide usarlo forwardRef
ya que la directiva se define después).
La directiva adjuntará un detector de tagsChange
eventos al evento del host (es decir, el componente al que se adjunta la directiva, es decir, el TagsComponent
). El onChange
mé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 writeValue
invoca cuando ngForm
se actualiza el valor enlazado en . Después de haber inyectado el componente adjunto (es decir, TagsComponent), podremos llamarlo para pasar este valor (ver el setValue
método anterior ).
No olvide proporcionar CUSTOM_VALUE_ACCESSOR
en 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 tags
de la empresa, el valid
atributo del companyForm.controls.tags
control se convierte false
automáticamente.
Consulte este artículo (sección "Componente compatible con NgModel") para obtener más detalles: