3

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?

1 Answer 1

1

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]
    }))
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Looks like a good enhancement for better encapsulation. Would you agree, that Custom Form Components are not a good fit for Components, that encapsulate part of a form? Of course, if one needs to build a Library that encapsulate functionality of typically one value (e.g. a custom datepicker), then Custom Form Components would be the way to go.
I think having a component that encapsulates the form group as well as any form validators is a valid use case. I could see the value in having an “Address” component that has multiple fields and supports passing in an address model, that I could reuse it from multiple locations

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.