如何在自定义元素上实现 ngModel?
How to implement ngModel on custom elements?
给定一个简单的 input
元素,我可以这样做:
<input [(ngModel)]="name" /> {{ name }}
这不适用于我的自定义元素:
<my-selfmade-combobox [(ngModel)]="name" values="getValues()" required></my-selfmade-combobox>
我该如何实施?
如果你真的需要 [(ngModel)]
(它支持 ngForm
,不像 [(myProp)]
方法),
我想这个 link 会回答你的问题:
我们需要执行两件事来实现:
- 提供表单组件逻辑的组件。它不需要输入,因为它将由 ngModel 本身提供
- 自定义
ControlValueAccessor
将实现此组件与 ngModel
/ ngControl
之间的桥梁
前面的link给你一个完整的例子...
我在我的共享组件中实现了一次 ngModel
输入,然后我可以非常简单地扩展它。
只有两行代码:
providers: [createCustomInputControlValueAccessor(MyInputComponent)]
extends InputComponent
我的-input.component.ts
import { Component, Input } from '@angular/core';
import { InputComponent, createCustomInputControlValueAccessor } from '../../../shared/components/input.component';
@Component({
selector: 'my-input',
templateUrl: './my-input-component.component.html',
styleUrls: ['./my-input-component.scss'],
providers: [createCustomInputControlValueAccessor(MyInputComponent)]
})
export class MyInputComponent extends InputComponent {
@Input() model: string;
}
我的-input.component.html
<div class="my-input">
<input [(ngModel)]="model">
</div>
input.component.ts
import { Component, forwardRef, ViewChild, ElementRef, OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
export function createCustomInputControlValueAccessor(extendedInputComponent: any) {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => extendedInputComponent),
multi: true
};
}
@Component({
template: ''
})
export class InputComponent implements ControlValueAccessor, OnInit {
@ViewChild('input') inputRef: ElementRef;
// The internal data model
public innerValue: any = '';
// Placeholders for the callbacks which are later provided
// by the Control Value Accessor
private onChangeCallback: any;
// implements ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
// implements ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
// implements ControlValueAccessor interface - not used, used for touch input
registerOnTouched() { }
// change events from the textarea
private onChange() {
const input = <HTMLInputElement>this.inputRef.nativeElement;
// get value from text area
const newValue = input.value;
// update the form
this.onChangeCallback(newValue);
}
ngOnInit() {
const inputElement = <HTMLInputElement>this.inputRef.nativeElement;
inputElement.onchange = () => this.onChange();
inputElement.onkeyup = () => this.onChange();
}
}
第 1 步: 添加下面的 providers
属性:
@Component({
selector: 'my-cool-element',
templateUrl: './MyCool.component.html',
styleUrls: ['./MyCool.component.css'],
providers: [{ // <================================================ ADD THIS
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCoolComponent),
multi: true
}]
})
步骤 2: 实施 ControlValueAccessor
:
export class MyCoolComponent implements ControlValueAccessor {
private _value: string;
// Whatever name for this (myValue) you choose here, use it in the .html file.
public get myValue(): string { return this._value }
public set myValue(v: string) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
constructor() {}
onChange = (_) => { };
onTouched = () => { };
writeValue(value: any): void {
this.myValue = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error("Method not implemented.");
}
}
步骤 3: 在 html 中,绑定你想要的任何控件 myValue
:
<my-cool-element [(value)]="myValue">
<!-- ..... -->
</my-cool-element>
[(ngModel)]="item"
是 shorthand 对于 [ngModel]="item" (ngModelChange)="item = $event"
这意味着如果你想添加一个双向绑定 属性 到你的组件,例如
<app-my-control [(myProp)]="value"></app-my-control>
您需要在组件中做的就是添加
@Input()
myProp: string;
// Output prop name must be Input prop name + 'Change'
// Use in your component to write an updated value back out to the parent
@Output()
myPropChange = new EventEmitter<string>();
@Input
将处理写入并将新值写回父级,只需调用 this.myPropChange.emit("Awesome")
(您可以将发射放在 setter 中属性 如果你只是想确保每次值更改时它都会更新。)
您可以阅读更详细的解释 how/why 它有效 here。
如果您想使用名称 ngModel
(因为有额外的指令绑定到带有 ngModel
的元素),或者这是针对 FormControl
元素而不是组件(又名,用于 ngForm
),那么您将需要使用 ControlValueAccessor
。可以阅读 here.
关于制作自己的 FormControl
及其工作原理的详细说明
您可以自己实现自定义双向绑定。对于 angular 10,请参见官方示例 SizerComponent,此处 [(size)]
的行为与 [(ngModel)]
:
相同
<app-sizer [(size)]="fontSizePx"></app-sizer>
给定一个简单的 input
元素,我可以这样做:
<input [(ngModel)]="name" /> {{ name }}
这不适用于我的自定义元素:
<my-selfmade-combobox [(ngModel)]="name" values="getValues()" required></my-selfmade-combobox>
我该如何实施?
如果你真的需要 [(ngModel)]
(它支持 ngForm
,不像 [(myProp)]
方法),
我想这个 link 会回答你的问题:
我们需要执行两件事来实现:
- 提供表单组件逻辑的组件。它不需要输入,因为它将由 ngModel 本身提供
- 自定义
ControlValueAccessor
将实现此组件与ngModel
/ngControl
之间的桥梁
前面的link给你一个完整的例子...
我在我的共享组件中实现了一次 ngModel
输入,然后我可以非常简单地扩展它。
只有两行代码:
providers: [createCustomInputControlValueAccessor(MyInputComponent)]
extends InputComponent
我的-input.component.ts
import { Component, Input } from '@angular/core';
import { InputComponent, createCustomInputControlValueAccessor } from '../../../shared/components/input.component';
@Component({
selector: 'my-input',
templateUrl: './my-input-component.component.html',
styleUrls: ['./my-input-component.scss'],
providers: [createCustomInputControlValueAccessor(MyInputComponent)]
})
export class MyInputComponent extends InputComponent {
@Input() model: string;
}
我的-input.component.html
<div class="my-input">
<input [(ngModel)]="model">
</div>
input.component.ts
import { Component, forwardRef, ViewChild, ElementRef, OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
export function createCustomInputControlValueAccessor(extendedInputComponent: any) {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => extendedInputComponent),
multi: true
};
}
@Component({
template: ''
})
export class InputComponent implements ControlValueAccessor, OnInit {
@ViewChild('input') inputRef: ElementRef;
// The internal data model
public innerValue: any = '';
// Placeholders for the callbacks which are later provided
// by the Control Value Accessor
private onChangeCallback: any;
// implements ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
// implements ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
// implements ControlValueAccessor interface - not used, used for touch input
registerOnTouched() { }
// change events from the textarea
private onChange() {
const input = <HTMLInputElement>this.inputRef.nativeElement;
// get value from text area
const newValue = input.value;
// update the form
this.onChangeCallback(newValue);
}
ngOnInit() {
const inputElement = <HTMLInputElement>this.inputRef.nativeElement;
inputElement.onchange = () => this.onChange();
inputElement.onkeyup = () => this.onChange();
}
}
第 1 步: 添加下面的 providers
属性:
@Component({
selector: 'my-cool-element',
templateUrl: './MyCool.component.html',
styleUrls: ['./MyCool.component.css'],
providers: [{ // <================================================ ADD THIS
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCoolComponent),
multi: true
}]
})
步骤 2: 实施 ControlValueAccessor
:
export class MyCoolComponent implements ControlValueAccessor {
private _value: string;
// Whatever name for this (myValue) you choose here, use it in the .html file.
public get myValue(): string { return this._value }
public set myValue(v: string) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
constructor() {}
onChange = (_) => { };
onTouched = () => { };
writeValue(value: any): void {
this.myValue = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error("Method not implemented.");
}
}
步骤 3: 在 html 中,绑定你想要的任何控件 myValue
:
<my-cool-element [(value)]="myValue">
<!-- ..... -->
</my-cool-element>
[(ngModel)]="item"
是 shorthand 对于 [ngModel]="item" (ngModelChange)="item = $event"
这意味着如果你想添加一个双向绑定 属性 到你的组件,例如
<app-my-control [(myProp)]="value"></app-my-control>
您需要在组件中做的就是添加
@Input()
myProp: string;
// Output prop name must be Input prop name + 'Change'
// Use in your component to write an updated value back out to the parent
@Output()
myPropChange = new EventEmitter<string>();
@Input
将处理写入并将新值写回父级,只需调用 this.myPropChange.emit("Awesome")
(您可以将发射放在 setter 中属性 如果你只是想确保每次值更改时它都会更新。)
您可以阅读更详细的解释 how/why 它有效 here。
如果您想使用名称 ngModel
(因为有额外的指令绑定到带有 ngModel
的元素),或者这是针对 FormControl
元素而不是组件(又名,用于 ngForm
),那么您将需要使用 ControlValueAccessor
。可以阅读 here.
FormControl
及其工作原理的详细说明
您可以自己实现自定义双向绑定。对于 angular 10,请参见官方示例 SizerComponent,此处 [(size)]
的行为与 [(ngModel)]
:
<app-sizer [(size)]="fontSizePx"></app-sizer>