DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป is a community of 963,864 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Jennifer Wadella for Bitovi

Posted on • Updated on • Originally published at jenniferwadella.com

Angular Forms Several Ways: Reactive, Nested, Across Routes

Dealing with forms is rarely simple anymore. Many SaaS products offer highly customizable setups managed by complex and dynamic forms. Knowing how to use Angulars ControlContainer will give you more control, heh, over managing your forms.

ControlContainer

The ControlContainer is a base class for form directives that contain multiple registered instances of NgControl. We can use the ControlContainer to access FormControls, FormGroups, and FormArrays and manage a main form chunked across components.

A common situation is having a group of form controls, like an "address" groups of fields like "street", "city", "zip" that you use repeatedly across your application. To use bind a Reactive FormControl to the DOM we need access to that FormControl - and can use the FormGroup directive to pass a main FormGroup instance to nested components.

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

@Component({
  selector: 'app-span-form',
  template: `
  <form [formGroup]="sampleForm">
    <div class="form-group">
      <label for="name">First Name</label>
      <input name="first_name" formControlName="first_name" />
    </div>
    <div class="form-group">
      <label for="name">Last Name</label>
      <input name="last_name" formControlName="last_name" />
    </div>
    <div class="form-group">
      <label for="name">Email Address</label>
      <input name="email" formControlName="email" />
    </div>
    <app-address></app-address>
  </form>
  `,
  styleUrls: ['./span-form.component.less']
})
export class SpanFormComponent implements OnInit {
  public sampleForm: FormGroup;
  constructor(
    private fb: FormBuilder
  ) { }

  ngOnInit() {
    this.sampleForm = this.fb.group({
      user_name: ['', Validators.required],
      first_name: ['',Validators.required],
      last_name: ['',Validators.required],
      email: ['',Validators.required],
      street: ['',Validators.required],
      city: ['',Validators.required],
      state: ['',Validators.required],
      zip: ['',Validators.required]
    })
  }

}
Enter fullscreen mode Exit fullscreen mode

Inside our reusable Address component we can access the sampleForm by injecting the ControlContainer class in our constructor - this will return the parent FormGroupDirective and allow us to access that control. From there we can use the formControlName for our FormControls as expected.

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

@Component({
  selector: 'app-address',
  template: `
  <form  *ngIf="ogFormGroup" [formGroup]="ogFormGroup">
    <h5>Address:</h5>
    <div class="form-group">
      <label for="name">Street Name</label>
      <input formControlName="street" />
    </div>
    <div class="form-group">
      <label for="name">City</label>
      <input formControlName="city" />
    </div>
    <div class="form-group">
      <label for="name">State</label>
      <input formControlName="state" />
    </div>
    <div class="form-group">
      <label for="name">Zip</label>
      <input formControlName="zip" />
    </div>
  </form>
  `,
  styleUrls: ['./address.component.less']
})
export class AddressComponent implements OnInit {
  public ogFormGroup;
  constructor(public controlContainer: ControlContainer) {
  }

  ngOnInit() {
    this.ogFormGroup = this.controlContainer.control;
  }

}
Enter fullscreen mode Exit fullscreen mode

We can use the same approach in other situations, for example a form that's presented across multiple paths.

//parent component where form is initialized
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-span-form',
  template: `
    <button routerLink="step-1">Step 1</button>
    <button routerLink="step-2">Step 2</button>
    <button routerLink="step-3">Step 3</button>
    <form [formGroup]="mainForm" class="form-ui">
      <router-outlet></router-outlet>
    </form>
  `,
  styleUrls: ['./span-form.component.less']
})
export class SpanFormComponent implements OnInit {
  public mainForm: FormGroup;
  constructor(
    private fb: FormBuilder
  ) { }

  ngOnInit() {
    this.mainForm = this.fb.group({
      user_name: ['', Validators.required],
      first_name: ['',Validators.required],
      last_name: ['',Validators.required],
      email: ['',Validators.required],
      address: this.fb.group({
        street: ['',Validators.required],
        city: ['',Validators.required],
        state: ['',Validators.required],
        zip: ['',Validators.required]
      }),
      favorite_color: ['',Validators.required],
      favorite_food: ['',Validators.required],
      favorite_season: ['',Validators.required],
      favorite_episode: ['',Validators.required]
    })
  }

}
Enter fullscreen mode Exit fullscreen mode

In the child component we access the parent formDirective through the ControlContainer class and bind it to a new form directive in our component and include the formControls we want to present to the user.

// child component at nested route displaying part of main form:
import { Component, OnInit } from '@angular/core';
import { ControlContainer } from '@angular/forms';

@Component({
  selector: 'app-step1',
  template: `
    <h3>Step One of Our Sign-in Process</h3>
    <form [formGroup]="parentForm">
      <div class="form-group">
        <label for="name">Choose a User Name</label>
        <input name="user_name" formControlName="user_name" />
      </div>
    </form>
    <button [disabled]="!parentForm.controls.user_name.valid"
    routerLink="/signup/step-2">Next</button>
  `,
  styleUrls: ['./step1.component.less']
})
export class Step1Component implements OnInit {
  public parentForm;
  constructor(private controlContainer: ControlContainer) {
  }

  ngOnInit() {
    this.parentForm = this.controlContainer.control;
  }
}
Enter fullscreen mode Exit fullscreen mode

Code demo here: https://github.com/tehfedaykin/ControlContainerExample

Cheers from a dairy farm ๐Ÿฎin New Zealand!

Top comments (0)

Classic DEV Post from 2020:

js visualized

๐Ÿš€โš™๏ธ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! ๐Ÿฅณ

Happy coding!