角度:使用ControlValueAccessor进行自定义输入

时间:2019-06-19 13:20:43

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

我不确定如何将自定义组件包装在另一个组件下。

赞:

ComponentA_withForm
|
--ComponentA1_withWrapperOfCustomInput
  |
  --ComponentA11_withCustomInput

如果我有这样的结构:

ComponentA_withForm
|
--ComponentA11_withCustomInput

一切都很好

但是对于我的情况(大量异步数据),我需要一个包装器...可以通过某种方式做到这一点吗?

这是我的小提琴代码:

ComponentA:

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) { }
}

ComponentA1:

import { Component } from '@angular/core';

@Component({
  selector: 'custom-input-wrapper',
  template: '<custom-input></custom-input>',
})
export class CustomInputWrapperComponent {
  constructor() { }
}

ComponentA11:

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的肮脏解决方案:)

2 个答案:

答案 0 :(得分:1)

由于您不想要肮脏的解决方案;),因此也可以在ControlValueAccessor中实现CustomInputWrapperComponent。这样,只需几行代码,父项中的任何更改都将反映在子级中,子级中的任何更改也将反映在父级中。

包装器组件

@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

答案 1 :(得分:1)

您不能在formControlName上使用custom-input-wrapper,因为它没有实现ControlValueAccessor。在ControlValueAccessor上实现custom-input-wrapper可能是一种解决方案,但这似乎有些过分。而是将控件作为custom-input-wrapper从formGroup传递到@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