5

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...

2
  • Can you post your code in the question? What exact problem are you having? Commented Nov 15, 2017 at 0:22
  • @roelofs Thank you, I updated post. Commented Nov 15, 2017 at 1:06

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

7 Comments

wow, I have to use fb.control instead of fb.group. I feel it weird because its instance is referenced as FormGroup, but It's working!! Thank you @12seconds .
fmm, if FormArray is nested I got error. What is the problem? stackblitz.com/edit/angular-krhrcz
@adwd I think it is natural that custom control i a control. :) Value of custom control is just complex object.
@penleychan There is a problem with validation. If we treat nested form as FormControl then the main form is not aware of the nested form validity. Here is demo stackblitz.com/edit/angular-pmfq5z?file=app%2Fapp.component.ts. Try to clear required fields. How can we resolve this?
@TarasHupalo, I suggest you have a look at my other answers to achieve this if you stackoverflow.com/a/53053333/6736888
|
0

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

2 Comments

I'm fine with a down vote but it'd be nice to get an explanation why you think I wrote a bad answer.
Hello. Validation does not work fully in sub-components. The required mat-form-field does not get highlighted when clicking on submit. I would like to have the ability to click on submit even if the form is invalid, that way you clearly see what you missed filling the forms. Do you have a suggestion for this problem? I tried with CVA but it does not work either. One solution is have a ViewChild for the subcomponent, create a function so the subcomponent can call it's form markAllTouched function. But I feel this is a bit hacky.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.