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',
'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'
}
}
});
<!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>
<my-app>
loading...
</my-app>
</body>
</html>
<h1>Angular 4: Reactive forms(Nested forms)</h1>
<form [formGroup]="myForm" (ngSubmit)="register(myForm)" novalidate>
<p> Is "myForm" valid? {{myForm.valid}} </p>
<div>
<label>Name</label>
<input type="text" name="name" formControlName="name">
<show-errors [control]="myForm.controls.name"></show-errors>
</div>
<div>
<label>Birth Year</label>
<input type="text" name="birthYear" formControlName="birthYear">
<show-errors [control]="myForm.controls.birthYear"></show-errors>
</div>
<div formGroupName="location">
<h3>Location</h3>
<div>
<label>Country</label>
<input type="text" name="country" formControlName="country">
<show-errors [control]="myForm.controls.location.controls.country"></show-errors>
</div>
<div>
<label>City</label>
<input type="text" name="city" formControlName="city">
</div>
<show-errors [control]="myForm.controls.location"></show-errors>
</div>
<div formArrayName="phoneNumbers">
<h3>Phone numbers</h3>
<div *ngFor="let phoneNumberControl of myForm.controls.phoneNumbers.controls; let i=index;">
<label>Phone number {{i + 1}}</label>
<input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i">
<button type="button" (click)="remove(i); myForm.controls.phoneNumbers.markAsTouched()">remove</button>
<show-errors [control]="phoneNumberControl"></show-errors>
</div>
<button type="button" (click)="add(); myForm.controls.phoneNumbers.markAsTouched()">Add phone number</button>
<show-errors [control]="myForm.controls.phoneNumbers"></show-errors>
</div>
<pre>{{myForm.value | json}}</pre>
<button type="submit" [disabled]="myForm.invalid || myForm.pending">Register</button>
<button type="button" (click)="printMyForm()">Print to console</button>
</form>
import { FormGroup, FormControl, FormArray, Validators, NgForm } from '@angular/forms';
import { Component, OnInit } from '@angular/core';
import { CustomValidators } from './validators/custom-validators';
@Component({
selector: 'my-app',
templateUrl: 'src/app.component.html'
})
export class AppComponent implements OnInit {
private myForm: FormGroup;
constructor() {
}
ngOnInit() {
this.myForm = new FormGroup({
'name': new FormControl('', Validators.required, CustomValidators.uniqueName),
'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]),
'location': new FormGroup({
'country': new FormControl('', Validators.required),
'city': new FormControl()
}, CustomValidators.countryCity),
'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()], CustomValidators.telephoneNumbers)
});
}
remove(i: number) {
(<FormArray>this.myForm.controls.phoneNumbers).removeAt(i);
}
add() {
(<FormArray>this.myForm.controls.phoneNumbers).push(this.buildPhoneNumberComponent());
}
buildPhoneNumberComponent() {
return new FormControl('', [Validators.required, CustomValidators.telephoneNumber]);
}
printMyForm() {
console.log(this.myForm);
}
register(myForm: NgForm) {
console.log('Registration successful.');
console.log(myForm.value);
}
}
import { ReactiveFormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ShowErrorsComponent } from './show-errors.component';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent, ShowErrorsComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
import { Component, Input } from '@angular/core';
import { AbstractControlDirective, AbstractControl } from '@angular/forms';
@Component({
selector: 'show-errors',
template: `
<ul *ngIf="shouldShowErrors()">
<li style="color: red" *ngFor="let error of listOfErrors()">{{error}}</li>
</ul>
`,
})
export class ShowErrorsComponent {
private static readonly errorMessages = {
'required': () => 'This field is required',
'minlength': (params) => 'The min number of characters is ' + params.requiredLength,
'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength,
'pattern': (params) => 'The required pattern is: ' + params.requiredPattern,
'years': (params) => params.message,
'countryCity': (params) => params.message,
'uniqueName': (params) => params.message,
'telephoneNumbers': (params) => params.message,
'telephoneNumber': (params) => params.message
};
@Input()
private control: AbstractControlDirective | AbstractControl;
shouldShowErrors(): boolean {
return this.control &&
this.control.errors &&
(this.control.dirty || this.control.touched);
}
listOfErrors(): string[] {
return Object.keys(this.control.errors)
.map(field => this.getMessage(field, this.control.errors[field]));
}
private getMessage(type: string, params: any) {
return ShowErrorsComponent.errorMessages[type](params);
}
}
* {
outline: none;
font-family: Arial, serif;
font-size: 15px;
}
h1 {
font-size: 20px;
}
form, [ngForm], ngForm, [ng-reflect-form], [ngModelGroup], [formGroupName], [formArrayName] {
margin: 10px 10px 10px 0;
padding: 10px;
border-radius: 5px;
border: 1px solid #46C5F9;
display: block;
}
label {
display: inline-block;
width: 150px;
margin-left: 10px;
padding-bottom: 10px;
}
input {
padding: 3px 4px;
border-radius: 3px;
border: 1px solid #DFDFDF;
}
input:focus {
border: solid 1px #707070;
box-shadow: 0 0 5px 1px #969696;
}
[rootNestableForm] h3 {
color: blue;
}
.form-state {
color:red;
}
.form-state.valid {
color:green;
}
p {
margin-left: 10px;
}
pre {
padding: 10px;
background-color: #F8F8F9;
color: #aaa;
white-space: pre;
border-radius: 5px;
font: 13px 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace;
}
button {
box-shadow: inset 0 0 0 0 #54a3f7;
background: linear-gradient(to bottom, #007dc1 5%, #0061a7 100%);
border: none;
border-radius: 6px;
display: inline-block;
cursor: pointer;
color: #ffffff;
font-weight: bold;
padding: 3px 25px;
text-decoration: none;
}
button:hover {
background: #0061a7 linear-gradient(to bottom, #0061a7 5%, #007dc1 100%);
}
button:active {
position: relative;
top: 1px;
}
button[disabled] {
background: #B9B9B9;
}
import { FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms';
export class CustomValidators {
static birthYear(c: FormControl): ValidationErrors {
const numValue = Number(c.value);
const currentYear = new Date().getFullYear();
const minYear = currentYear - 85;
const maxYear = currentYear - 18;
const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear;
const message = {
'years': {
'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear
}
};
return isValid ? null : message;
}
static countryCity(form: FormGroup): ValidationErrors {
const countryControl = form.get('country');
const cityControl = form.get('city');
if (countryControl != null && cityControl != null) {
const country = countryControl.value;
const city = cityControl.value;
let error = null;
if (country === 'France' && city !== 'Paris') {
error = 'If the country is France, the city must be Paris';
}
const message = {
'countryCity': {
'message': error
}
};
return error ? message : null;
}
}
static uniqueName(c: FormControl): Promise<ValidationErrors> {
const message = {
'uniqueName': {
'message': 'The name is not unique'
}
};
return new Promise(resolve => {
setTimeout(() => {
resolve(c.value === 'Existing' ? message : null);
}, 1000);
});
}
static telephoneNumber(c: FormControl): ValidationErrors {
const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value);
const message = {
'telephoneNumber': {
'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)'
}
};
return isValidPhoneNumber ? null : message;
}
static telephoneNumbers(form: FormGroup): ValidationErrors {
const message = {
'telephoneNumbers': {
'message': 'At least one telephone number must be entered'
}
};
const phoneNumbers = form.controls;
const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers).length > 0;
return hasPhoneNumbers ? null : message;
}
}