Насправді є дві речі для реалізації:
- Компонент, який забезпечує логіку компонента форми. Він не потребує введення, оскільки він буде наданий
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"):