W rzeczywistości należy wdrożyć dwie rzeczy:
- Składnik zapewniający logikę komponentu formularza. Nie potrzebuje danych wejściowych, ponieważ zostanie dostarczony
ngModelsamodzielnie
- 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]
});
}
}
TagsComponentKomponent definiuje logikę dodawania i usuwania elementów w tagsliś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 setValuejeden (nazwa nie jest tutaj ważna). Użyjemy go później do podania wartości z elementu ngModeldo 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 ControlValueAccessorinterfejs. Dostawca musi być zdefiniowany dla tego akcesora wartości względem NG_VALUE_ACCESSORtokenu (nie zapomnij go użyć, forwardRefponieważ dyrektywa jest zdefiniowana później).
Dyrektywa dołączy detektor zdarzeń do tagsChangezdarzenia hosta (tj. Komponentu, do którego jest dołączona dyrektywa, tj. TagsComponent). onChangeMetoda 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 writeValueJest wywoływana, gdy wartość związana w ngFormjest aktualizowana. Po wstrzyknięciu dołączonego komponentu (tj. TagsComponent), będziemy mogli wywołać go w celu przekazania tej wartości (zobacz poprzednią setValuemetodę).
Nie zapomnij podać CUSTOM_VALUE_ACCESSORw 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łą tagsfirmę, plikvalid atrybut companyForm.controls.tagskontroli staje się falseautomatycznie.
Więcej informacji można znaleźć w tym artykule (sekcja „Komponent zgodny z NgModel”):