dagerber
dagerber

Reputation: 231

Reusable, nested Subcomponents in Forms (embedded Components) with Validation

Let's say we have an address and want to reuse it in multiple Forms (e.g. Person, Company, ...) In Angular, everything is components, so we should probably write a component.

What is the best way, to do so? Turns out, it's not that easy. It should encapsulate the data and also validate the embedded form fields. I found two solutions to the problem:

1. Custom Form Component

What I don't like about it: overly complex, everything is delegated inside the Subcomponent. With validation, you will need some kind of "inner Form" for the validation to work. The form controls must be defined in the parent, encapsulation is not really possible. Simple example, see: https://stackblitz.com/edit/angular-nested-form-custum-component-test

2. Component, that has two Inputs: FormGroup and form-submitted-state

Idea taken from https://medium.com/spektrakel-blog/angular2-building-nested-reactive-forms-7978ecd145e4

Much simpler than the Custom Form Component. We need to provide the FormGroup, that is built outside the nested component. If we want to show validation errors "onSubmit", we also need to provide the form's 'submitted state' to the Child Component. Simple example, see https://stackblitz.com/edit/angular-nested-formcomponent-test

Any comments or better ideas to solve the problem?

Upvotes: 3

Views: 1788

Answers (1)

Michael Kang
Michael Kang

Reputation: 52847

Demo

You can support encapsulation by passing the FormGroup to the nested control with an initial value, and allowing the nested control to define its own inner form group along with any validators.

app.component.html

<nested-form-cmp 
     init="foo" 
     [formSubmitted]="f.submitted" 
     [grp]="myForm">
</nested-form-cmp>

AppComponent would only need to initialize its own form data:

export class AppComponent  {

  myForm: FormGroup;

  constructor(private fb: FormBuilder) {
      this.myForm = fb.group({
        name: ['', Validators.required]
      })
  }

  submit(form: NgForm) {
    console.log("Reactive Form submitted: " + form.submitted);
  }
}

nested-form.component.html

The nested component would be responsible for creating its own nested FormGroup, and initializing it with validators:

<div [formGroup]="grp">
  <div formGroupName="innerGrp">
  <label>
    Inner name:
    <input formControlName="name2" type="text" id="outer"/>   
  </label>
  <span class="error" *ngIf="(formSubmitted || grp.get('innerGrp.name2').touched) && grp.get('innerGrp.name2').hasError('required')">
    Inner name is required
  </span>
  </div>
</div>

nested-form.component.ts

export class NestedFormComponent implements OnInit{

  // The FormGroup built in the parent
  @Input() public grp: FormGroup;
  @Input() public init: string;

  // Needed, because the FormGroup does not get the forms submitted state
  @Input() public formSubmitted: boolean;
  constructor(private fb: FormBuilder){ 

  }

  ngOnInit() {
    this.grp.setControl('innerGrp', this.fb.group({
      name2: [this.init, Validators.required]
    }))
  }
}

Upvotes: 1

Related Questions