Angular:使用 ControlValueAccessor 自定义输入
Posted
技术标签:
【中文标题】Angular:使用 ControlValueAccessor 自定义输入【英文标题】:Angular: custom input with ControlValueAccessor 【发布时间】:2019-11-02 06:04:00 【问题描述】:如果自定义组件是另一个组件下的包装器,我不确定如何使用它。
喜欢:
ComponentA_withForm
|
--ComponentA1_withWrapperOfCustomInput
|
--ComponentA11_withCustomInput
如果我有这样的结构:
ComponentA_withForm
|
--ComponentA11_withCustomInput
一切都好
但对于我的情况(大量异步数据),我需要一个包装器......有没有可能以某种方式做到这一点?
这是我的小提琴代码:
组件A:
import Component from '@angular/core';
import FormBuilder from '@angular/forms';
@Component(
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: formVal | json</p>`
)
export class AppComponent
form = this.fb.group(
someInput: [],
);
get formVal()
return this.form.getRawValue();
constructor(private fb: FormBuilder)
组件A1:
import Component from '@angular/core';
@Component(
selector: 'custom-input-wrapper',
template: '<custom-input></custom-input>',
)
export class CustomInputWrapperComponent
constructor()
组件A11:
import Component, forwardRef from '@angular/core';
import ControlValueAccessor, NG_VALUE_ACCESSOR from '@angular/forms';
@Component(
selector: 'custom-input',
template: `Hey there! <button (click)="inc()">Value: value </button>`,
providers: [
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true,
],
)
export class CustomInputComponent implements ControlValueAccessor
private value = 0;
writeValue(value: number): void
this.value = value;
registerOnChange(fn: (_: any) => void): void
this.onChangeFn = fn;
registerOnTouched(fn: any): void
inc()
this.value = this.value + 1;
this.onChangeFn(this.value);
onChangeFn = (_: any) => ;
这里我有一个工作示例: https://stackblitz.com/edit/angular-qmrj3a
所以:基本上删除和重构代码不使用CustomInputWrapperComponent
使我的代码工作。但我需要这个包装器,但我不确定如何传递formControlName
。
我不想要一个通过父 formGroup 的肮脏解决方案:)
【问题讨论】:
为什么不在你的CustomInputWrapperComponent
中实现ControlValueAccessor
呢?
@abd995我认为这也很肮脏:)
我认为这是您可能拥有的最干净的解决方案。传入表单控件或表单组是我猜的肮脏解决方案。
【参考方案1】:
由于您不想要一个肮脏的解决方案;),您也可以在 CustomInputWrapperComponent
中实现 ControlValueAccessor
。这样,父项的任何更改都将反映在子项中,子项的任何更改也将反映在父项中,只需几行代码。
包装器组件
@Component(
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="value"></custom-input>',
providers: [
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputWrapperComponent),
multi: true,
]
)
export class CustomInputWrapperComponent implements AfterViewInit, ControlValueAccessor
public value = new FormControl();
constructor()
ngAfterViewInit()
this.value.valueChanges.subscribe((x) =>
this.onChangeFn(x);
);
writeValue(value: number): void
this.value.setValue(value);
registerOnChange(fn: (_: any) => void): void
this.onChangeFn = fn;
registerOnTouched(fn: any): void
onChangeFn = (_: any) => ;
父模板
<form [formGroup]="form"><custom-input-wrapper formControlName="someInput"></custom-input-wrapper></form> <p>value is: formVal | json</p>
我在这里做了一个 stackbitz 演示 - https://stackblitz.com/edit/angular-csaxcz
【讨论】:
如果可能的话,您能否将代码的相关部分添加到您的答案中?如果链接不再有效,则仅链接的答案变得无关紧要,在 stackblitz 演示的情况下,它也是查看者找到相关部分的工作。我强烈建议您将 cmets 添加到代码中,以防您为其他人编写它们(例如有人在 Stack Overflow 上询问)。 @SaschaM78 我已将代码的某些部分添加到答案中。谢谢 非常感谢您添加相关代码部分,这使得答案更易于理解。 @abd995 感谢这个解决方案,效果很好。我唯一修改的是,不是使用valueChanges
来触发包装器组件的onchange fn,而是使用ViewChild
抓住内部组件,然后用它来触发onchange,例如:this.innerComponent.registerOnTouched(this.onTouched);
,因为我不想writeValue
触发 onchange。【参考方案2】:
你不能在custom-input-wrapper
上使用formControlName
,因为它没有实现ControlValueAccessor
。在custom-input-wrapper
上实施ControlValueAccessor
可能是一个解决方案,但它似乎有点矫枉过正。而是将控件从 formGroup 传递给 custom-input-wrapper
作为 @Input()
并将输入的 formControl 传递给 custom-input
app.component
@Component(
selector: 'my-app',
template: `<form [formGroup]="form"><custom-input-wrapper [formCtrl]="form.get('someInput')"></custom-input-wrapper></form> <p>value is: formVal | json</p>`
)
export class AppComponent
form = this.fb.group(
someInput: [],
);
get formVal()
return this.form.getRawValue();
constructor(private fb: FormBuilder)
custom-input-wrapper.component
@Component(
selector: 'custom-input-wrapper',
template: '<custom-input [formControl]="formCtrl"></custom-input>',
)
export class CustomInputWrapperComponent
@Input() formCtrl: AbstractControl;
constructor()
这是一个工作演示 https://stackblitz.com/edit/angular-3lrfqv
【讨论】:
正如 OP 已经提到的。I don't want a dirty solution with passing parent formGroup
。将 formGroup 作为输入传递可能不是他正在寻找的解决方案。
Angular 文档声明“ControlValueAccessor 定义了一个接口,充当 Angular 表单 API 和 DOM 中的本机元素之间的桥梁”。恕我直言,在CustomInputWrapperComponent
中实现ControlValueAccessor
是一个肮脏的解决方案,因为CustomInputWrapperComponent
中没有需要与Angular Forms API 桥接的本机元素。以上是关于Angular:使用 ControlValueAccessor 自定义输入的主要内容,如果未能解决你的问题,请参考以下文章
如何降级使用 angular2 编写的自定义管道以在 angular 1.5 中使用?
Angular 2 - 如何使用新的 Angular 2.0.0-rc.1 路由器
如何在 Angular 6 中使用 angular-timelinejs3?