W rzeczywistości należy wdrożyć dwie rzeczy:
- Składnik zapewniający logikę komponentu formularza. Nie potrzebuje danych wejściowych, ponieważ zostanie dostarczony
ngModel
samodzielnie
- Niestandardowy
ControlValueAccessor
, który zaimplementuje most między tym komponentem a ngModel
/ngControl
Weźmy próbkę. Chcę zaimplementować komponent zarządzający listą tagów dla firmy. Komponent pozwoli na dodawanie i usuwanie tagów. Chcę dodać weryfikację, aby upewnić się, że lista tagów nie jest pusta. Zdefiniuję to w moim komponencie, jak opisano poniżej:
(...)
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]
});
}
}
TagsComponent
Komponent definiuje logikę dodawania i usuwania elementów w tags
liście.
@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 = '';
}
}
Jak widać, w tym komponencie nie ma wejścia, ale setValue
jeden (nazwa nie jest tutaj ważna). Użyjemy go później do podania wartości z elementu ngModel
do komponentu. Ten komponent definiuje zdarzenie, które ma być powiadamiane o aktualizacji stanu komponentu (listy tagów).
Zaimplementujmy teraz połączenie między tym komponentem a ngModel
/ ngControl
. Odpowiada to dyrektywie implementującej ControlValueAccessor
interfejs. Dostawca musi być zdefiniowany dla tego akcesora wartości względem NG_VALUE_ACCESSOR
tokenu (nie zapomnij go użyć, forwardRef
ponieważ dyrektywa jest zdefiniowana później).
Dyrektywa dołączy detektor zdarzeń do tagsChange
zdarzenia hosta (tj. Komponentu, do którego jest dołączona dyrektywa, tj. TagsComponent
). onChange
Metoda zostanie wywołana, gdy wystąpi zdarzenie. Ta metoda odpowiada metodzie zarejestrowanej przez Angular2. W ten sposób będzie świadomy zmian i odpowiednio aktualizuje powiązaną kontrolkę formularza.
Plik writeValue
Jest wywoływana, gdy wartość związana w ngForm
jest aktualizowana. Po wstrzyknięciu dołączonego komponentu (tj. TagsComponent), będziemy mogli wywołać go w celu przekazania tej wartości (zobacz poprzednią setValue
metodę).
Nie zapomnij podać CUSTOM_VALUE_ACCESSOR
w wiązaniach dyrektywy.
Oto pełny kod niestandardowego 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; }
}
W ten sposób, gdy usunę całą tags
firmę, plikvalid
atrybut companyForm.controls.tags
kontroli staje się false
automatycznie.
Więcej informacji można znaleźć w tym artykule (sekcja „Komponent zgodny z NgModel”):