The Rock
The Rock

Reputation: 363

Splitting a reactive form into multiple components with validation

Can we split a reactive form into multiple components?.

For instance, The buttons (SAVE, UPDATE, and DELETE) in the parent component and the form is placed in child component.

How one can achieve this?

are there any examples?

Thanks

Upvotes: 1

Views: 4690

Answers (3)

NIDHIN VINCENT
NIDHIN VINCENT

Reputation: 235

I hope that you need to trigger the form events like submit, reset etc.. from the parent component.

  • Steps to achieve

    • Provide some uniqueId for those buttons and make it display:none in the child component.
    • Using label for you can bind the click event to those buttons from the parent component.
  • example

    • child component

      <form (submit)="_onSubmit($event)">
         <button type="submit" id="submitBTN_id"> submit</button>
       </form>
      
    • parent component

      <a>
        <label for="submitBTN_id">
          Submit
        </label>
      </a>
      

Upvotes: 0

bryan60
bryan60

Reputation: 29325

when I've done this in the past, I will generally use a service to manage and distribute the form.

@Injectable()
export class MyFormService {
  private _form: FormGroup;
  constructor(private fb: FormBuilder) {
    this.buildForm()
  }

  private buildForm() {
    this._form = this.fb.group({
      // whatever form specifics in here
    })
  }

  get form() {
    return this._form
  }

  // whatever of these you need
  get childForm() {
    return this.form.get('childFormKey')
  }

  // any other appropriate methods or observables exposed
}

parent component:

@Component({
  .. component stuff ..
  providers: [MyFormService] // might as well provide it here to keep it private
})
export class ParentComponent {
  constructor(private formService: MyFormService) {

  }

  onSubmit() {
    const value = this.formService.form.value;
    // do the submit
  }
  // any methods you need

}

child

export class ChildComponent {
  childForm: FormGroup

  constructor(private formService: MyFormService) {
    this.childForm = this.formService.childForm;
    // do whatever else you need for this component
  }
}

this method is most applicable to big complex forms that you want to break into smaller less complex forms, but things that won't be highly reusable (though you can still get good reusability out with this method if you set things up correctly). It's also great for situations where you want the same overall form, but different templates.

Things that are more "widget" like, and you intend to use in many forms, will probably want a different method.

Upvotes: 2

ionut-t
ionut-t

Reputation: 1177

You can split a reactive form into multiple sub-forms but you need to make use of the ControlValueAccessor and its friend and you need to nest it in a parent form from where you submit it. And of course, you need to add some additional boilerplate in order to have all the validation in place and everything else that reactive forms require.

You can have as many sub-forms as you want this way.

Below you can see an example of how you can do it in the Angular way:

Child Component

import { Component, OnInit, forwardRef } from '@angular/core';
import {
  NG_VALUE_ACCESSOR,
  NG_VALIDATORS,
  ControlValueAccessor,
  Validator,
  FormGroup,
  FormBuilder,
  AbstractControl,
  ValidationErrors,
  Validators
} from '@angular/forms';

@Component({
  ...
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ChildComponent),
      multi: true
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => ChildComponent),
      multi: true
    }
  ]
})
export class ChildComponent implements OnInit, ControlValueAccessor, Validator {
  public childForm: FormGroup;
  public onTouched: () => void = () => {};

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.childForm = this.fb.group({
     ...
    });
  }

  public writeValue(val: any): void {
    return val && this.childForm.setValue(val, { emitEvent: false });
  }

  public registerOnChange(fn: any): void {
    this.childForm.valueChanges.subscribe(fn);
  }

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

  public setDisabledState?(isDisabled: boolean): void {
    isDisabled ? this.childForm.disable() : this.childForm.enable();
  }

  public validate(control: AbstractControl): ValidationErrors | null {
    return this.childForm.valid
      ? null
      : { invalidForm: { valid: false, message: 'Form is invalid' } };
  }
}

Child Component template:

<ng-container [formGroup]="childForm">
.....
</ng-container>

Parent Component ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  ...
})
export class StandardSetupFormComponent implements OnInit {
  parentForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.parentForm = this.fb.group({
      child: null,
      ...
    });
  }

 public onSubmit() {
  ...
 }
}

Parent component template:

<form class="form" [formGroup]="parentForm" (ngSubmit)="onSubmit()">

  <app-child formControlName="child"></app-child>

  <button[disabled]="parentForm.invalid">Submit</button>
</form>

As you can see in the parent component you need to treat the child form as a form control.

There are other ways that you can do it. By using inputs for example and even if it looks simpler it's not the correct way.

I've read a great article about what I've shown you above a while ago, but I can't find it yet. I'll update the answer if I do.

Here is the article that will give you a better and deeper understanding of nesting reactive forms.

Upvotes: 4

Related Questions