"No value accessor for..." 采用模型驱动形式
"No value accessor for..." with model-driven form
我正在尝试使用 Material 设计组件构建一个临时下拉菜单,但无法弄清楚为什么我的 ControlValueAccessor
无法正常工作。这是代码的相关部分:
import {
AfterViewInit,
Component,
ElementRef,
forwardRef,
Input,
OnInit,
Provider,
ViewChild
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
CORE_DIRECTIVES
} from '@angular/common';
import { MdCard } from '@angular2-material/card';
import {
MdInput,
MD_INPUT_DIRECTIVES
} from '@angular2-material/input';
import { MD_LIST_DIRECTIVES } from '@angular2-material/list';
declare var module: {
id: string;
};
export const MD_SELECT_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => MdSelect), multi: true });
const noop = () => {};
@Component({
selector: 'md-select',
moduleId: module.id,
template: `
<div>
<md-input readOnly type="text" [placeholder]="placeholder" (click)="selectClick()">
<i md-suffix class="fa fa-sort-desc"></i>
</md-input>
<md-card [ngClass]="{ visible: menuVisible }" (blur)="menuBlur()">
<md-list>
<md-list-item class="md-option" *ngFor="let option of options" (click)="optionClick(option)" [ngClass]="{ 'selected': option.selected }">
{{option.name}}
</md-list-item>
</md-list>
</md-card>
</div>
`,
styleUrls: [
'md-select.component.css'
],
directives: [
CORE_DIRECTIVES,
MdCard,
MdInput,
MD_INPUT_DIRECTIVES,
MD_LIST_DIRECTIVES
],
providers: [MD_SELECT_VALUE_ACCESSOR]
})
export class MdSelect implements ControlValueAccessor {
@Input() multiple: boolean;
@Input() placeholder: string;
private _value: string;
onChanged: (_: any) => void = noop;
onTouched: () => void = noop;
options: MdOption[] = [];
menuVisible: boolean = false;
selectedOption: MdOption;
private _selectedOptions: MdOption[] = [];
addOption(option: MdOption) {
this.options.push(option);
if (option.selected && (!this.selectedOption || this.multiple)) {
this.selectedOption = option;
this.value = this.selectedOption.name;
}
}
selectClick() {
if (!this.menuVisible) {
this.menuVisible = true;
}
}
optionClick(option: MdOption) {
if (option) {
if (this.multiple) {
option.selected = !option.selected;
} else {
this.options.filter(option => option.selected).forEach(option => option.selected = false);
option.selected = true;
}
this.onChanged('value');
}
this.menuBlur();
}
menuBlur() {
this.menuVisible = false;
}
get value(): string {
return this.options.filter(option => option.selected).map(option => option.name).join(', ')
}
set value(value: string) {
if (value !== this._value) {
this._value = value; // TODO
this.onChanged('value');
}
}
writeValue(value: any): void {
console.log('writeValue("' + value + '")')
this.value = value;
}
registerOnChange(fn: (_: any) => void): void {
this.onChanged = (_: any) => { console.log('onChange("' + _ + '")'); fn(_); };
}
registerOnTouched(fn: () => void): void {
this.onTouched = () => { console.log('onTouched()'); fn(); }
}
}
@Component({
selector: 'md-option',
template: `
<div #wrapper>
<ng-content></ng-content>
</div>
`
})
export class MdOption implements AfterViewInit {
@ViewChild('wrapper') wrapper: ElementRef;
@Input() disabled: boolean;
name: string;
@Input() selected: boolean;
@Input() value: string;
constructor(private select: MdSelect) { }
ngAfterViewInit() {
if (this.wrapper) {
let name = this.wrapper.nativeElement.innerHTML;
this.name = name ? name.trim() : 'EMPTY';
}
this.select.addOption(this);
}
}
这是模板中使用它的部分
<div class="md-form-control">
<md-select placeholder="Shift" class="shift" formControlName="shift">
<md-option *ngFor="let s of shifts" [value]="s.id" [ngValue]="s.id"
[selected]="s.id === shift.id">
{{s.name}}
</md-option>
</md-select>
</div>
这是表单设置
private initForm() {
...
this.form = this.formBuilder.group({
shift: [this.shift.name],
...
})
}
如果我尝试 运行 这个代码,我会得到
platform-browser.umd.js:1900 ORIGINAL EXCEPTION: No value accessor for 'shift'
我错过了什么?
我在实现 MD textarea 控件并使用 @angular2-material
中的 MdInput
检查时找到了原因。
目前有 2 个桶包含 NG_VALUE_ACCCESSOR
和 ControlValueAccessor
、@angular/common
和 @angular/forms
。我猜想 新形式 他们将访问器的东西移到了 forms
而 common
中的旧实现仍然留给那些还没有切换的人。
但是,如果您碰巧导入了错误的组件,则不会有任何警告。
因此,如果您正在使用新表单模块,解决方案是更改
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
DefaultValueAccessor
} from '@angular/common';
到
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
DefaultValueAccessor
} from '@angular/forms';
您可能还需要更改
export const YOUR_CUSTOM_CONTROL_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => YourCustomControlAccessor), multi: true });
到
export const YOUR_CUSTOM_CONTROL_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => YourCustomControlAccessor),
multi: true
};
我正在尝试使用 Material 设计组件构建一个临时下拉菜单,但无法弄清楚为什么我的 ControlValueAccessor
无法正常工作。这是代码的相关部分:
import {
AfterViewInit,
Component,
ElementRef,
forwardRef,
Input,
OnInit,
Provider,
ViewChild
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
CORE_DIRECTIVES
} from '@angular/common';
import { MdCard } from '@angular2-material/card';
import {
MdInput,
MD_INPUT_DIRECTIVES
} from '@angular2-material/input';
import { MD_LIST_DIRECTIVES } from '@angular2-material/list';
declare var module: {
id: string;
};
export const MD_SELECT_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => MdSelect), multi: true });
const noop = () => {};
@Component({
selector: 'md-select',
moduleId: module.id,
template: `
<div>
<md-input readOnly type="text" [placeholder]="placeholder" (click)="selectClick()">
<i md-suffix class="fa fa-sort-desc"></i>
</md-input>
<md-card [ngClass]="{ visible: menuVisible }" (blur)="menuBlur()">
<md-list>
<md-list-item class="md-option" *ngFor="let option of options" (click)="optionClick(option)" [ngClass]="{ 'selected': option.selected }">
{{option.name}}
</md-list-item>
</md-list>
</md-card>
</div>
`,
styleUrls: [
'md-select.component.css'
],
directives: [
CORE_DIRECTIVES,
MdCard,
MdInput,
MD_INPUT_DIRECTIVES,
MD_LIST_DIRECTIVES
],
providers: [MD_SELECT_VALUE_ACCESSOR]
})
export class MdSelect implements ControlValueAccessor {
@Input() multiple: boolean;
@Input() placeholder: string;
private _value: string;
onChanged: (_: any) => void = noop;
onTouched: () => void = noop;
options: MdOption[] = [];
menuVisible: boolean = false;
selectedOption: MdOption;
private _selectedOptions: MdOption[] = [];
addOption(option: MdOption) {
this.options.push(option);
if (option.selected && (!this.selectedOption || this.multiple)) {
this.selectedOption = option;
this.value = this.selectedOption.name;
}
}
selectClick() {
if (!this.menuVisible) {
this.menuVisible = true;
}
}
optionClick(option: MdOption) {
if (option) {
if (this.multiple) {
option.selected = !option.selected;
} else {
this.options.filter(option => option.selected).forEach(option => option.selected = false);
option.selected = true;
}
this.onChanged('value');
}
this.menuBlur();
}
menuBlur() {
this.menuVisible = false;
}
get value(): string {
return this.options.filter(option => option.selected).map(option => option.name).join(', ')
}
set value(value: string) {
if (value !== this._value) {
this._value = value; // TODO
this.onChanged('value');
}
}
writeValue(value: any): void {
console.log('writeValue("' + value + '")')
this.value = value;
}
registerOnChange(fn: (_: any) => void): void {
this.onChanged = (_: any) => { console.log('onChange("' + _ + '")'); fn(_); };
}
registerOnTouched(fn: () => void): void {
this.onTouched = () => { console.log('onTouched()'); fn(); }
}
}
@Component({
selector: 'md-option',
template: `
<div #wrapper>
<ng-content></ng-content>
</div>
`
})
export class MdOption implements AfterViewInit {
@ViewChild('wrapper') wrapper: ElementRef;
@Input() disabled: boolean;
name: string;
@Input() selected: boolean;
@Input() value: string;
constructor(private select: MdSelect) { }
ngAfterViewInit() {
if (this.wrapper) {
let name = this.wrapper.nativeElement.innerHTML;
this.name = name ? name.trim() : 'EMPTY';
}
this.select.addOption(this);
}
}
这是模板中使用它的部分
<div class="md-form-control">
<md-select placeholder="Shift" class="shift" formControlName="shift">
<md-option *ngFor="let s of shifts" [value]="s.id" [ngValue]="s.id"
[selected]="s.id === shift.id">
{{s.name}}
</md-option>
</md-select>
</div>
这是表单设置
private initForm() {
...
this.form = this.formBuilder.group({
shift: [this.shift.name],
...
})
}
如果我尝试 运行 这个代码,我会得到
platform-browser.umd.js:1900 ORIGINAL EXCEPTION: No value accessor for 'shift'
我错过了什么?
我在实现 MD textarea 控件并使用 @angular2-material
中的 MdInput
检查时找到了原因。
目前有 2 个桶包含 NG_VALUE_ACCCESSOR
和 ControlValueAccessor
、@angular/common
和 @angular/forms
。我猜想 新形式 他们将访问器的东西移到了 forms
而 common
中的旧实现仍然留给那些还没有切换的人。
但是,如果您碰巧导入了错误的组件,则不会有任何警告。
因此,如果您正在使用新表单模块,解决方案是更改
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
DefaultValueAccessor
} from '@angular/common';
到
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
DefaultValueAccessor
} from '@angular/forms';
您可能还需要更改
export const YOUR_CUSTOM_CONTROL_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, { useExisting: forwardRef(() => YourCustomControlAccessor), multi: true });
到
export const YOUR_CUSTOM_CONTROL_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => YourCustomControlAccessor),
multi: true
};