Angular 6 Template-driven form with multiple components

In this post I am gonna show you how to create an Angular 6 Template-driven form that spans across multiple components.

Here is a working example for the same and the explanation is given below.

First of all we need to create the main component that contains the form. So the code for main form component is as follows:-

Template for main component (hero-form.component.html)

<div class="container">
    <h1>Hero Form</h1>
    <form #heroForm="ngForm">
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" class="form-control" id="name"
                   required
                   [(ngModel)]="model.name" name="name">
        </div>
        <div class="form-group">
            <label for="alterEgo">Alter Ego</label>
            <input type="text" class="form-control" id="alterEgo"
                   [(ngModel)]="model.alterEgo" name="alterEgo">
        </div>
        <div class="form-group">
            <label for="power">Hero Power</label>
            <select class="form-control" id="power"
                    required
                    [(ngModel)]="model.power" name="power">
                <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
            </select>
        </div>
        <side-kick></side-kick>
        <button type="submit" class="btn btn-success">Submit</button>
    </form>
    <pre>{{ heroForm.value | json }}</pre>
</div>

Main component class (hero-form.component.ts)

import { Component } from '@angular/core';

@Component({
    selector: 'app-hero-form',
    templateUrl: './hero-form.component.html',
    styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent {
    powers = ['Power1', 'Power2', 'Power3', 'Power4'];

    model = new Hero(1, 'Test Hero', this.powers[0], 'Power1');
}

export class Hero {
    constructor(
        public id: number,
        public name: string,
        public power: string,
        public alterEgo?: string
    ) { }
}

Notice the highlighted "<side-kick></side-kick>" tag in the above mentioned "hero-form.component.html" template it is the child component that contains remaining part of the form. The sidekick component is described below :-

Sidekick component template (side-kick.component.html)

<fieldset ngModelGroup="sidekickDetails">
    <div class="form-group">
        <label for="sidekick-name">Sidekick Name</label>
        <input class="form-control" id="sidekick-name" type="text" name="sidekickName" [(ngModel)]="sidekickName">
    </div>

    <div class="form-group">
        <label for="sidekick-name">Sidekick Age</label>
        <input class="form-control" id="sidekick-age" type="text" name="sidekickAge" [(ngModel)]="sidekickAge">
    </div>
</fieldset>

Sidekick component class (side-kick.component.ts)

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

@Component({
    selector: 'side-kick',
    templateUrl: './side-kick.component.html',
    styleUrls: ['./side-kick.component.css'],
    viewProviders: [{ provide: ControlContainer, useExisting: NgForm }]
})
export class SideKickComponent implements OnInit {
    sidekickName: string = "";
    sidekickAge: string = "";

    constructor() { }

    ngOnInit() {

    }
}

Notice the "viewProviders" array highlighted in "side-kick.component.ts" file, this is where all the magic happens. If you won't provide the "ControlContainer" in the "viewProviders" array then you will get this error in the browser console :-


So what's exactly happening here is that we are telling the Angular Injector to inject the existing instance of NgForm (i.e. the form in its parent hero-form.component) whenever it is asked to provide for "ControlContainer", which is otherwise provided from within the "NgForm" when we create a form in a component, but since we are not creating any form in side-kick component that's why we need to provide it in the "viewProviders" array.



1 comment:

  1. This is very helpful! How would you access the form control within the child component for validation, conditional logic, etc?

    ReplyDelete