Насправді є дві речі для реалізації:
- Компонент, який забезпечує логіку компонента форми. Він не потребує введення, оскільки він буде наданий
ngModelсам
- Спеціальний,
ControlValueAccessorякий реалізує міст між цим компонентом та ngModel/ngControl
Візьмемо зразок. Я хочу реалізувати компонент, який керує списком тегів для компанії. Компонент дозволить додавати та видаляти теги. Я хочу додати перевірку, щоб переконатися, що список тегів не порожній. Я визначу це у своєму компоненті, як описано нижче:
(...)
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Компонент визначає логіку додавання і видалення елементів в tagsсписку.
@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 = '';
}
}
Як бачите, у цьому компоненті немає жодного введення, крім setValueодного (назва тут не важлива). Ми використовуємо його пізніше, щоб надати значення від ngModelдо компоненту. Цей компонент визначає подію для повідомлення про оновлення стану компонента (список тегів).
Давайте реалізуємо зараз зв’язок між цим компонентом та ngModel/ ngControl. Це відповідає директиві, яка реалізує ControlValueAccessorінтерфейс. Постачальник повинен бути визначений для цього доступу до значення проти NG_VALUE_ACCESSORмаркера (не забувайте використовувати, forwardRefоскільки директива визначається після).
Директива приєднує прослуховувач подій до tagsChangeподії хоста (тобто компонент, до якого прикріплена директива, тобто TagsComponent). onChangeМетод буде викликатися при виникненні події. Цей метод відповідає методу, зареєстрованому Angular2. Таким чином він буде в курсі змін та відповідних оновлень відповідного контролю форми.
writeValueВикликається , коли значення , пов'язане в ngFormоновленні. Після введення компонента, приєднаного до (тобто TagsComponent), ми зможемо викликати його для передачі цього значення (див. Попередній setValueметод).
Не забудьте вказати CUSTOM_VALUE_ACCESSORв прив'язках директиви.
Ось повний код спеціального 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; }
}
Таким чином, коли я видаляю всю tagsкомпанію, validатрибут companyForm.controls.tagsконтролю стає falseавтоматично.
Детальніше див. У цій статті (розділ "Компонент, сумісний з NgModel"):