【问题标题】:Angular: custom input with ControlValueAccessorAngular:使用 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我认为这也很肮脏:)
  • 我认为这是您可能拥有的最干净的解决方案。传入表单控件或表单组是我猜的肮脏解决方案。

标签: javascript angular typescript components angular-reactive-forms


【解决方案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 桥接的本机元素。
猜你喜欢
  • 2020-06-03
  • 1970-01-01
  • 2021-01-23
  • 2019-09-01
  • 1970-01-01
  • 2019-01-18
  • 2016-10-02
  • 2016-04-29
  • 2018-06-06
相关资源
最近更新 更多