Home > database >  How to mark nested angular form as touched implementing ControlValueAccessor?
How to mark nested angular form as touched implementing ControlValueAccessor?

Time:03-25

There is an example form Angular University how to use nested forms with ControlValueAccessor.

They create a separate form component and use it as a sub-form:

<div [formGroup]="form">
  ... other form controls
  <address-form formControlName="address" legend="Address"></address-form>
</div>

I want to have all the fields of the nested form marked as touched if I call the markAllAsTouched method on the parent form.

<form [formGroup]="form">
  <app-address-form formControlName="address"></app-address-form>      
</form>
<button (click)="markAsTouched()">Mark as touched</button>
export class AppComponent {
  public form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = fb.group({ address: fb.control({ city: '' }) });
  }

  public markAsTouched() {
    this.form.markAllAsTouched();
  }
}

Is it possible using this approach?

I have built a simplified version on StackBlitz.

What actually happens when you click the button is marking the app-address-form as touched:

<app-address-form formcontrolname="address" >
...
</app-address-form>

But I want to propogate it to the child form and its fields.

CodePudding user response:

Really the mannage of nested form groups that explain the link is a bit confused because you really not have nested form groups. You has a FormGroup with one control (the value of the control is an object but you only has one control)

See that in your example you write:

 this.form = fb.group({ address: fb.control({ city: '' }) });

A nested formGroup should be like:

 this.form = fb.group({ address: fb.group({ city: '' }) });

But this formGroup can not be mannage in the way Angular University say. The way is simply use a component and pass the formGroup as Input(*)

It's the reason because only is touched the "control"

SOLUTION:You can use a template reference variable and pass to your function

<form [formGroup]="form">
  <app-address-form #address formControlName="address"></app-address-form>      
</form>
<button (click)="markAsTouched(address)">Mark as touched</button>

  public markAsTouched(address:any) {
    this.form.markAllAsTouched();
    //see that you can access to the form of the "adress" simply
    //using adress.form
    address.form.markAllAsTouched()
  }

(*) the component (that not implements from ControlValueAccesor) is simply

@Component({
  selector: 'app-address-form',
  template: `
    <div [formGroup]="form">
      <label>City</label>
      <input formControlName="city" (blur)="onTouched()">
    </div>
  `
})
export class AddressFormComponent {
    form:FormGroup
    @Input('form') set _(value){
      this.form=value as FormGroup
    }
}

And you use as

<app-address-form [form]=form.get('address')><app-address-form>

CodePudding user response:

Probably there is a better way but, you could achieve this by using an Input

Parent component ts

export class AppComponent {
  public form: FormGroup;
  setAsTouched: boolean = false;

  constructor(private fb: FormBuilder) {
    this.form = fb.group({ address: fb.control({ city: '' }) });
  }

  public markAsTouched() {
    this.setAsTouched = true;
  }
}

Parent template component

<h1>Form</h1>
<form [formGroup]="form">
  <app-address-form
    formControlName="address"
    [mark]="setAsTouched"
  ></app-address-form>
</form>
<button (click)="markAsTouched()">Mark as touched</button>

Child component ts

...
...
export class AddressFormComponent implements ControlValueAccessor, Validator {
  @Input() set mark(isTouched: boolean) {
    if (isTouched) {
      this.form.markAllAsTouched();
    }
  }

  ...
  ...
  ...
}
  • Related