<!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;
};