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