Angular: composite ControlValueAccessor to impleme

2020-01-25 10:30发布

问题:

Composition of ControlValueAccessor to implement nested form is introduced in an Angular Connect 2017 presentation.

https://docs.google.com/presentation/d/e/2PACX-1vTS20UdnMGqA3ecrv7ww_7CDKQM8VgdH2tbHl94aXgEsYQ2cyjq62ydU3e3ZF_BaQ64kMyQa0INe2oI/pub?slide=id.g293d7d2b9d_1_1532

In this presentation, the speaker showed a way to implement custom form control which have multiple value (not only single string value but has two string field, like street and city). I want to implement it but I'm stuck. Sample app is here, does anybody know what should I correct?

https://stackblitz.com/edit/angular-h2ehwx

parent component

@Component({
  selector: 'my-app',
  template: `
    <h1>Form</h1>
    <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" novalidate>
      <label>name</label>
      <input formControlName="name">
      <app-address-form formControlName="address"></app-address-form>
      <button>submit</button>
    </form>
  `,
})
export class AppComponent  {
  @Input() name: string;
  submitData = '';
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = fb.group({
      name: 'foo bar',
      address: fb.group({
        city: 'baz',
        town: 'qux',
      })
    });
  }

  onSubmit(v: any) {
    console.log(v);
  }
}

nested form component

@Component({
  selector: 'app-address-form',
  template: `
    <div [formGroup]="form">
      <label>city</label>
      <input formControlName="city" (blur)="onTouched()">
      <label>town</label>
      <input formControlName="town" (blur)="onTouched()">
    </div>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => AddressFormComponent)
    }
  ]
})
export class AddressFormComponent implements ControlValueAccessor {
  form: FormGroup;

  onTouched: () => void = () => {};

  writeValue(v: any) {
    this.form.setValue(v, { emitEvent: false });
  }

  registerOnChange(fn: (v: any) => void) {
    this.form.valueChanges.subscribe(fn);
  }

  setDisabledState(disabled: boolean) {
    disabled ? this.form.disable() : this.form.enable();
  }

  registerOnTouched(fn: () => void) {
    this.onTouched = fn;
  }
}

and error message I got

ERROR TypeError: Cannot read property 'setValue' of undefined
at AddressFormComponent.writeValue (address-form.component.ts:32)
at setUpControl (shared.js:47)
at FormGroupDirective.addControl (form_group_directive.js:125)
at FormControlName._setUpControl (form_control_name.js:201)
at FormControlName.ngOnChanges (form_control_name.js:114)
at checkAndUpdateDirectiveInline (provider.js:249)
at checkAndUpdateNodeInline (view.js:472)
at checkAndUpdateNode (view.js:415)
at debugCheckAndUpdateNode (services.js:504)
at debugCheckDirectivesFn (services.js:445)

I think FormGroup instance should be injected to nested form component somehow...

回答1:

Couple issues, on your AppComponent change your FormBuilder to:

this.form = fb.group({
  name: 'foo bar',
  address: fb.control({ //Not using FormGroup
    city: 'baz',
    town: 'qux',
  })
});

On your AddressFormComponent you need to initialize your FormGroup like so:

form: FormGroup = new FormGroup({
    city: new FormControl,
    town: new FormControl
});

Here's the fork of your sample: https://stackblitz.com/edit/angular-np38bi



回答2:

We (at work) encountered that issue and tried different things for months: How to properly deal with nested forms.

Indeed, ControlValueAccessor seems to be the way to go but we found it very verbose and it was quite long to build nested forms. As we're using that pattern a lot within our app, we've ended up spending some time to investigate and try to come up with a better solution. We called it ngx-sub-form and it's a repo available on NPM (+ source code on Github).

Basically, to create a sub form all you have to do is extends a class we provide and also pass your FormControls. That's it.

We've updated our codebase to use it and we're definitely happy about it so you may want to give a try and see how it goes for you :)

Everything is explained in the README on github.

PS: We also have a full demo running here https://cloudnc.github.io/ngx-sub-form



标签: forms angular