<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <title>angular2 playground</title>
    <link rel="stylesheet" href="style.css" />
    <script src="https://unpkg.com/core-js@2.4.1/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="config.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
      <app-nested-form>
          loading...
      </app-nested-form>
  </body>

</html>
/* Styles go here */

### Angular Starter Plunker - Typescript
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {
    
    'app': './src',
    
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
    
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    
    'lodash': 'npm:lodash/lodash.js',
    'rxjs': 'npm:rxjs',
    'typescript': 'npm:typescript@2.0.2/lib/typescript.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    }
  }
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NestedFormsModule} from './app';

platformBrowserDynamic().bootstrapModule(NestedFormsModule)
import {
    BrowserModule 
} from '@angular/platform-browser';
import { 
    AfterViewInit,
    ChangeDetectorRef,
    Component,
    Input,
    OnInit,
    NgModule 
} from '@angular/core';
import { 
    CommonModule
} from '@angular/common';
import { 
    FormArray,
    FormBuilder,
    FormGroup,
    FormsModule, 
    ReactiveFormsModule,
    Validators
} from '@angular/forms';

import * as _ from 'lodash';

export interface ParentData {
    parentField1: string;
    parentField2: string;
    parentHiddenField1: string;
    children: ChildData[];
};

export interface ChildData {
    id: number;
    childField1: string;
    childField2: string;
    childHiddenField1: string;
    kid: InfantData[];
};

export interface InfantData {
    id: number;
    infantField: string;
};

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

@Component({
  selector: 'app-nested-form',
  templateUrl: './nested-form.component.html'
})
export class NestedFormComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

@Component({
    selector: 'app-parent-form',
    templateUrl: './parent-form.component.html',
    styleUrls: [ './parent-form.component.css' ]
})
export class ParentFormComponent implements OnInit, AfterViewInit {
    public initialState: ParentData;
    public parentData: ParentData;
    public parentForm: FormGroup;

    constructor(private fb: FormBuilder) {}

    ngOnInit() {
        this.initialState = this.getParentData();
        this.parentData = _.cloneDeep(this.initialState);
        this.parentForm = this.toFormGroup(this.parentData);
        console.log('Initial parentData', this.parentData);
    }

    ngAfterViewInit() {
        this.parentForm.valueChanges
            .subscribe(value => {
                console.log('Parent Form changed', value);
                this.parentData = _.mergeWith(this.parentData,
                                              value,
                                              this.mergeCustomizer);

            });
    }

    private getParentData(): ParentData {
        return {
            parentField1: 'Parent Field 1 Value',
            parentField2: 'Parent Field 2 Value',
            parentHiddenField1: 'Parent Hidden Field 1 Value',
            children: [{
                id: 1,
                childField1: 'Child 1 - Child Field 1 Value',
                childField2: 'Child 1 - Child Field 2 Value',
                childHiddenField1: 'Child 1 - Child Hidden Field 1 Value',
                kid: [{
                    id:Math.floor(Math.random() * 100) ,
                    infantField: 'test1'
                }]
            }, {
                id: 2,
                childField1: 'Child 2 - Child Field 1 Value',
                childField2: 'Child 2 - Child Field 2 Value',
                childHiddenField1: 'Child 2 - Child Hidden Field 1 Value',
                kid: [{
                    id:Math.floor(Math.random() * 100) ,
                    infantField: 'test2'
                }]
            }]
        };
    }

    private toFormGroup(data: ParentData): FormGroup {
        const formGroup = this.fb.group({
            parentField1: [ data.parentField1, Validators.required ],
            parentField2: [ data.parentField2, Validators.required ],
            parentHiddenField1: [ data.parentHiddenField1 ]
        });

        return formGroup;
    }

    // _.mergeWith customizer to avoid merging primitive arrays, and only
    // merge object arrays
    private mergeCustomizer = (objValue, srcValue) => {
        if (_.isArray(objValue)) {
            if (_.isPlainObject(objValue[0]) || _.isPlainObject(srcValue[0])) {
                return srcValue.map(src => {
                    const obj = _.find(objValue, { id: src.id });
                    return _.mergeWith(obj || {}, src, this.mergeCustomizer);
                });
            }
            return srcValue;
        }
    }

    onSubmit() {
        if (!this.parentForm.valid) {
            console.error('Parent Form invalid, preventing submission');
            return false;
        }

        const updatedParentData = _.mergeWith(this.parentData,
                                              this.parentForm.value,
                                              this.mergeCustomizer);

        console.log('Submitting...');
        console.log('Original parentData', this.initialState);
        console.log('Updated parentData', updatedParentData);

        return false;
    }

}


@Component({
    selector: 'app-child-list',
    templateUrl: './child-list.component.html',
    styleUrls: [ './child-list.component.css' ]
})
export class ChildListComponent implements OnInit {
    @Input('parentForm')
    public parentForm: FormGroup;

    @Input('children')
    public children: ChildData[];

    constructor(private cd: ChangeDetectorRef) { }

    ngOnInit() {
        console.log('Initializing child list', this.children);
        this.parentForm.addControl('children', new FormArray([]));
    }

    addChild() {
        const child: ChildData = {
            id: Math.floor(Math.random() * 100),
            childField1: '',
            childField2: '',
            childHiddenField1: '',
            kid: []
        };

        this.children.push(child);
        this.cd.detectChanges();
        return false;
    }

    removeChild(idx: number) {
        if (this.children.length > 1) {
            this.children.splice(idx, 1);
            (<FormArray>this.parentForm.get('children')).removeAt(idx);
        }
        return false;
    }
}

@Component({
    selector: 'app-child-form',
    templateUrl: './child-form.component.html',
    styleUrls: [ './child-form.component.css' ]
})
export class ChildFormComponent implements OnInit {
    @Input('children')
    public children: FormArray;

    @Input('child')
    public child: ChildData;

    public childForm: FormGroup;

    constructor(private fb: FormBuilder) {}

    ngOnInit() {
        console.log('Initializing child form', this.child);
        this.childForm = this.toFormGroup(this.child);
        this.children.push(this.childForm);
    }

    private toFormGroup(data: ChildData) {
        const formGroup = this.fb.group({
            id: [ data.id ],
            childField1: [ data.childField1 || '', Validators.required ],
            childField2: [ data.childField2 || '', Validators.required ],
            childHiddenField1: [ data.childHiddenField1 ]
        });

        return formGroup;
    }

}
/* kid -from*/
@Component({
    selector: 'app-kid-form',
    templateUrl: './kid-form.component.html',
    styleUrls: [ './kid-form.component.css' ]
})
export class KidFormComponent implements OnInit {
    @Input('kid')
    public kid: FormArray;

    @Input('infant')
    public infant: InfantData;

   @Input('infantForm')
    public infantForm: FormGroup;

    public initialState: InfantData;

    constructor(
            private fb: FormBuilder
        ) {}


    ngOnInit() {
        console.log('Initializing kid form', this.infant);
        this.infantForm = this.toFormGroup(this.infant);
        this.kid.push(this.infantForm);
    }

    private toFormGroup(data: InfantData) {
        const formGroup = this.fb.group({
            id: [ data.id ],
            infantField: [ data.infantField || '', Validators.required ]
        });

        return formGroup;
    }

}

import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { FormArray, FormGroup } from '@angular/forms';

@Component({
    selector: 'app-kid-list',
    templateUrl: './kid-list.component.html',
    styleUrls: [ './kid-list.component.css' ]
})
export class KidListComponent implements OnInit {
    @Input('childForm')
    public childForm: FormGroup;

    @Input('kid')
    public kid: InfantData[];

    public initialState: InfantData;

    constructor(private cd: ChangeDetectorRef) { }

    ngOnInit() {
        console.log('Initializing kid list', this.kid);
        this.childForm.addControl('kid', new FormArray([]));

    }

    addInfant() {
        const infant: InfantData = {
            id: Math.floor(Math.random() * 100),
            infantField: ''
        };
        this.kid.push(infant);
        this.cd.detectChanges();
        return false;
    }
    
    removeInfant(idx: number) {
        if (this.kid.length > 1) {
            this.kid.splice(idx, 1);
            (<FormArray>this.childForm.get('kid')).removeAt(idx);
        }
        return false;
    }
}


@NgModule({
    imports: [
        BrowserModule,
        CommonModule,
        FormsModule,
        ReactiveFormsModule
    ],
    declarations: [
        NestedFormComponent,
        ParentFormComponent,
        ChildFormComponent,
        ChildListComponent,       
        KidFormComponent,
        KidListComponent
    ],
    bootstrap: [
        NestedFormComponent
    ]
})
export class NestedFormsModule { }
<h1>Nested Forms Example</h1>

<app-parent-form></app-parent-form>
<h2>
    Parent Form
</h2>

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

    <div>
        <label for="parentField1">Parent Field 1</label>
        <input formControlName="parentField1" />
    </div>

    <div>
        <label for="parentField2">Parent Field 2</label>
        <input formControlName="parentField2" />
    </div>

    <app-child-list
        [parentForm]="parentForm"
        [children]="parentData.children">
    </app-child-list>

    <button type="submit" [disabled]="!parentForm.valid">
        Submit
    </button>

</form>

<div class="parent-form-state">

    <p>Current parentForm State:</p>

    <pre>{{ parentData | json }}</pre>

</div>
<h3>
    Children
</h3>

<div class="child-list"
     [formGroup]="parentForm">

    <div formArrayName="children">

        <div *ngFor="let child of children; let idx=index ">

            Child {{child.id}}

            (<a href="" (click)="removeChild(idx)">Remove</a>)

            <app-child-form
                [children]="parentForm.controls.children"
                [child]="child">
            </app-child-form>

        </div>

        <a href="" (click)="addChild()">
            Add Child
        </a>

    </div>

</div>
<div class="child-form"
     [formGroup]="childForm">

    <div>
        <label for="childField1">Child Field 1</label>
        <input formControlName="childField1" />
    </div>

    <div>
        <label for="childField1">Child Field 2</label>
        <input formControlName="childField2" />
    </div>
    
        <app-kid-list 
            [childForm]="childForm"
            [kid]="child.kid"
            >
        </app-kid-list>

</div>
.child-form {
    border: 1px solid black;
    margin-bottom: 20px;
    padding: 20px;
}

input {
    width: 30%;
    min-width: 200px;
}
.child-list {
    margin-bottom: 20px;
}
.parent-form {
    border: 1px solid black;
    margin-bottom: 20px;
    padding: 20px;
}

.parent-form-state {
    padding: 10px;
}

input {
    width: 30%;
    min-width: 200px;
}
<div class="infant-form"
     [formGroup]="infantForm">

    <div>
        <label for="infantField">infant Field1</label>
        <input formControlName="infantField" />
    </div>
</div>

.kid-list {
    margin-bottom: 20px;
}
<h3>
    Kid
</h3>

<div class="kid-list"
     [formGroup]="childForm">

    <div formArrayName="kid">

        <div *ngFor="let infant of kid; let idx=index ">

            Infant {{infant.id}}

            (<a href="" (click)="removeInfant(idx)">Remove</a>)

            <app-kid-form
                [kid]="childForm.controls.kid"
                [infant]="infant">              
            </app-kid-form>
        
        </div>

        <button (click)="addInfant()">
            Add Infant
        </button>

    </div>

</div>
.infant-form {
    border: 1px solid black;
    margin-bottom: 20px;
    padding: 20px;
}

input {
    width: 30%;
    min-width: 200px;
}