На самом деле нужно реализовать две вещи:
- Компонент, который обеспечивает логику вашего компонента формы. Он не требует ввода, так как он будет предоставлен
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») для более подробной информации: