<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
    <base href="." />
    <title>angular playground</title>
    
     <link rel="stylesheet"
        href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.4/css/bootstrap.min.css"/>

    <link rel="stylesheet" href="style.css" />
   <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
    <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
    <script src="config.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
    <my-app>
    loading...
  </my-app>
  </body>

</html>
/* Styles go here */

.form-control.ng-invalid {border-left:5px solid red;}

.form-control:disabled,.checkbox-inline:disabled {
    display: none;
}
.required:after, .form-control.ng-invalid ~ label.checkbox-inline::after {
    content: " *";
    color: red;
}
.form-group {margin-bottom:1px;}

### Angular 5 Reactive Forms - dynamic FormArray
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js in angular.io)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "lib": ["es2015", "dom"],
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      // paths serve as alias
      'npm:': 'https://unpkg.com/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      'app': './src',

      // angular bundles
      '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
      '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/common/http': 'npm:@angular/common/bundles/common-http.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/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.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/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs@5.5.2',
      'rxjs/operators':            'npm:rxjs@5.5.2/operators/index.js',
      'tslib':                     'npm:tslib/tslib.js',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.4/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript@5.2.7/lib/plugin.js',
      'typescript':                'npm:typescript@2.4.2/lib/typescript.js',

    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts',
        meta: {
          './*.ts': {
            //loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });

})(this);

/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
import {NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {AppComponent} from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [ BrowserModule,BrowserAnimationsModule,FormsModule,ReactiveFormsModule ],
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}
//our root app component
import {Component, VERSION, OnInit} from '@angular/core';
import { FormsModule, Validators, ReactiveFormsModule, FormGroup, FormControl, FormBuilder, FormArray } from '@angular/forms';
import { Common } from './common';

class Item {
   constructor(
        private text: string,
        private value: number) { }
}

class FormControlMetadata {
   constructor(
        private checkboxName: string,
        private checkboxLabel: string,
        private associateControlName: string,
        private associateControlLabel: string,
        private associateControlType: string,
        private associateControlData: Array<Item>) { }
}

class FormObjectToApi {
   constructor(
        private lastName: string,
        private firstName: string,
        private email: string,
        private selectedLanguages: Array<Item>) { }
}

@Component({
  selector: 'my-app',
  templateUrl: './sample-reactiveform.html',
  styleUrls: ['./style.css']
})

export class AppComponent implements OnInit {
  const regEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  test: any;
  name:string;
  selectedLanguageCount:number = 0;
  missingLanguage:boolean = false;
  sampleForm: FormGroup;
  langControlMetada: Array<FormControlMetadata> = [];
  programmingFormArray: FormArray;
  
  programmingLanguageList: Array<Item> = [];
  otherProgrammingLanguageList: Array<Item> = [];
  phpVersionList: Array<Item> = [];
  
  constructor(private formBuilder: FormBuilder) {
    this.name = `Angular v${VERSION.full}:`
  }
  
  ngOnInit() {
    this.sampleForm = this.formBuilder.group({
            firstName: new FormControl('', Validators.required),
            lastName: new FormControl('', Validators.required),
            email: new FormControl('', [Validators.required,Validators.pattern(this.regEmail)]),
            programmingLanguage: this.formBuilder.array([{}])
        });
    
     //populate programming languages source
     this.programmingLanguageList = [ 
        new Item('PHP',1),
        new Item('JavaScript',2),
        new Item('C#',3),
        new Item('Other',4)
      ];
      
     this.otherProgrammingLanguageList = [ 
      new Item('Python',1),
      new Item('Ruby',2),
      new Item('C++',3),
      new Item('Rust',4)
    ];
    
    this.phpVersionList = [ 
      new Item('v4',1),
      new Item('v5',2),
      new Item('v6',3),
      new Item('v7',4)
    ];
    
    this.populateProgrammingLanguage();
    
  }
  
  programmingLanguages(): FormArray {
    return this.sampleForm.get('programmingLanguage') as FormArray;
  };
  
  populateProgrammingLanguage() {
    //get the property
    this.programmingFormArray = this.programmingLanguages();
    
    //clear
    this.programmingFormArray.removeAt(0);

    let p:Item;
    //loop through the list and create the formarray metadata
    for (p of this.programmingLanguageList) {
      
      let control = new FormControlMetadata();
      let group = this.formBuilder.group({});
      
      //create the checkbox and other form element metadata
      control.checkboxName = `${Common.CheckboxPrefix}${p.value}`;
      control.checkboxLabel = p.text;
      control.associateControlName = `${Common.OtherPrefix}${p.value}`;
      control.associateControlLabel = `${p.text} comments`;
      control.associateControlType = Common.ControlType[Common.ControlType.textbox];
      
      //assume 1 is radio button list
       if (p.value == 1) {
          control.associateControlType = Common.ControlType[Common.ControlType.radioButtonList];
          control.associateControlData = this.phpVersionList;
      }
      
      //just assumed id 4 is dropdown
      if (p.value == 4) {
          control.associateControlType = Common.ControlType[Common.ControlType.dropdown];
          control.associateControlData = this.otherProgrammingLanguageList;
      }
      
      //store in array, use by html to loop through
      this.langControlMetada.push(control);
      
      //form contol
      let checkBoxControl = this.formBuilder.control('');
      let associateControl = this.formBuilder.control({ value: '', disabled: true });

      //add to form group [key, control]
      group.addControl(`${Common.CheckboxPrefix}${p.value}`, checkBoxControl);
      group.addControl(`${Common.OtherPrefix}${p.value}`, associateControl);
    //  this.test = group; 
      //add to form array
      this.programmingFormArray.push(group);
    }
  }
  
  //add/remove validator 
  languageSelectionChange(pos: number, cnkName: string, txtName: string) {
    let programmingLanguage = this.programmingLanguages();

    let control = programmingLanguage.controls[pos] as FormGroup

    if (control.controls[cnkName].value == true) {
        //checkbox checked
        control.controls[txtName].enable();
        control.controls[txtName].setValidators([Validators.required]);
        control.controls[txtName].updateValueAndValidity();
        this.selectedLanguageCount++;
    }
    else {
        //unchecked
        control.controls[txtName].setValue('');
        control.controls[txtName].disable();
        control.controls[txtName].clearValidators();
        control.controls[txtName].updateValueAndValidity();
        this.selectedLanguageCount--;
    }
  }
  
  //submit click
  public submit(e: any): void {
    e.preventDefault();
    
     //reset 
    let selectedLanguageList: Array<Item> = [];
    let programmingLanguage = this.programmingLanguages();
    let i: number;
    //checkbox id
    let languageId: number = 0;
    
     for (i = 0; i < programmingLanguage.controls.length; i++) {

        let control = programmingLanguage.controls[i] as FormGroup
        let selectedLanguage: Language = {} as any;

        //get the selected checkbox id
        for (var k in control.controls) {
            languageId = Number(k.replace(/[a-zA-Z_]/g, ""));
            break;
        }

        //capture the selected checkbox Id and textbox value
        if (control.controls[`${Common.CheckboxPrefix}${languageId}`].value == true) {
            selectedLanguage.value = languageId;
            selectedLanguage.text = control.controls[`${Common.OtherPrefix}${languageId}`].value
            selectedLanguageList.push(selectedLanguage);
        }
      }
    
    if (selectedLanguageList.length == 0) {
        this.missingLanguage = true;
    } else {
      //submit to API
      let formObjectToApi = new FormControlMetadata();
      
      formObjectToApi.lastName =this.sampleForm.controls['lastName'].value;
      formObjectToApi.firstName =this.sampleForm.controls['firstName'].value;
      formObjectToApi.email =this.sampleForm.controls['email'].value;
      formObjectToApi.selectedLanguages =selectedLanguageList;
   
      this.missingLanguage = false;
      this.test = formObjectToApi;
    }
  }
}














<div class="container-fluid">
    <h5>{{name}} ReactiveForm - FormArray</h5>
    <form novalidate [formGroup]="sampleForm">
        <div class="form-group row">
            <div class="col-xs-6">
                <label for="lastName" class="required">Last Name:</label>
                <input class="form-control form-control-sm" id="lastName"
                       placeholder="Last Name" maxlength="50" formControlName="lastName">
            </div>
        </div>
        <div class="form-group row">
            <div class="col-xs-6">
                <label for="firstName" class="required">First Name:</label>
                <input class="form-control form-control-sm" id="firstName"
                       placeholder="First Name" maxlength="50" formControlName="firstName">
            </div>
        </div>
        <div class="form-group row">
            <div class="col-xs-6">
                <label for="email" class="required">Email:</label>
                <input class="form-control form-control-sm" id="email"
                       placeholder="Email Address" maxlength="50" formControlName="email">
            </div>
        </div>
        
         <div class="form-group row">
            <div class="col-xs-12">
                <label class="required">Your favorite programming language:</label>
            </div>
        </div>

        <div class="form-group row" formArrayName="programmingLanguage">
          <div  class="col-xs-12"
             *ngFor="let item of langControlMetada; let i = index;">
            <div [formGroupName]="i">
                <div class="form-group row">

                    <div class="form-inline" style="margin-left:15px;">
                        <div class="form-check">
                            <label [for]="item.checkboxName" class="form-check-label">
                                <input type="checkbox" class="form-check-input" [id]="item.checkboxName"
                                       (change)="languageSelectionChange(i,item.checkboxName,item.associateControlName)"
                                       [formControlName]="item.checkboxName"> {{ item.checkboxLabel}} 
                            </label>

                            <input *ngIf="item.associateControlType == 'textbox'"
                                   class="form-control form-control-sm"
                                   id="item.associateControlName"
                                   [placeholder]="item.associateControlLabel" maxlength="255"
                                   [formControlName]="item.associateControlName" />
                            <span *ngIf="item.associateControlType == 'dropdown'">
                              <select class="form-control form-control-sm" 
                                    [formControlName]="item.associateControlName">
                                <option *ngFor="let item of item.associateControlData" 
                                    [value]="item.value">{{item.text}}</option>
                              </select>
                            </span>
                             <span *ngIf="item.associateControlType == 'radioButtonList'"  >
                                <span *ngFor="let option of item.associateControlData" >
                                      <input #version type="radio" [formControlName]="item.associateControlName"
                                        class="form-control form-control-sm"
                                        [value]="option.value">
                                     <label class="checkbox-inline" *ngIf="!version.disabled"> {{option.text}}</label>
                                </span>
                            </span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        </div>
        
        <span *ngIf="missingLanguage" style="color:red">Missing programming language!</span>
<hr/>
    <div class="form-group row">
        <div class="col-xs-12">
             Form is valid? - {{sampleForm.valid && selectedLanguageCount > 0}}
        </div>
    </div>
    <div class="form-group row">
        <div class="col-xs-12">
          <button class="btn btn-primary" (click)="submit($event)"
                  [disabled]="!sampleForm.valid || selectedLanguageCount == 0">
              Submit
          </button>
          
        </div>
    </div>
    <div class="form-group row">
        <div class="col-xs-12">
             Output: {{test | json}}
        </div>
    </div>
    </form>
</div>
enum ControlType {
    textbox =1 ,
    dropdown = 2,
    radioButtonList = 3
}

export class Common {
  public static ControlType = ControlType;
  public static CheckboxPrefix = 'cbLanguage_';
  public static OtherPrefix ='otherValue_';
}
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;

module.exports.translate = function(load){
  if (load.source.indexOf('moduleId') != -1) return load;

  var url = document.createElement('a');
  url.href = load.address;

  var basePathParts = url.pathname.split('/');

  basePathParts.pop();
  var basePath = basePathParts.join('/');

  var baseHref = document.createElement('a');
  baseHref.href = this.baseURL;
  baseHref = baseHref.pathname;

  if (!baseHref.startsWith('/base/')) { // it is not karma
    basePath = basePath.replace(baseHref, '');
  }

  load.source = load.source
    .replace(templateUrlRegex, function(match, quote, url){
      var resolvedUrl = url;

      if (url.startsWith('.')) {
        resolvedUrl = basePath + url.substr(1);
      }

      return 'templateUrl: "' + resolvedUrl + '"';
    })
    .replace(stylesRegex, function(match, relativeUrls) {
      var urls = [];

      while ((match = stringRegex.exec(relativeUrls)) !== null) {
        if (match[2].startsWith('.')) {
          urls.push('"' + basePath + match[2].substr(1) + '"');
        } else {
          urls.push('"' + match[2] + '"');
        }
      }

      return "styleUrls: [" + urls.join(', ') + "]";
    });

  return load;
};