docs(reactive-forms): add reactive forms guide (#2835)

This commit is contained in:
Kapunahele Wong 2017-01-31 20:36:32 -05:00 committed by Ward Bell
parent adec6ac6ce
commit 452c16dce2
53 changed files with 5235 additions and 5 deletions

View File

@ -43,7 +43,7 @@
</div>
<!-- #docregion submit-button -->
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>
<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
<!-- #enddocregion submit-button -->
<!-- #docregion new-hero-button-form-reset -->
<button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
@ -83,7 +83,7 @@
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
<button class="btn btn-primary" (click)="submitted=false">Edit</button>
</div>
<!-- #enddocregion submitted -->
</div>
@ -137,7 +137,7 @@
<!-- #enddocregion powers -->
<!-- #docregion start -->
<button type="submit" class="btn btn-default">Submit</button>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
@ -175,7 +175,7 @@
</div>
<!-- #enddocregion ngModel-2-->
<button type="submit" class="btn btn-default">Submit</button>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<div class="container">
<h1>Reactive Forms</h1>
<hero-detail></hero-detail>
</div>`
})
export class AppComponent { }

View File

@ -0,0 +1,13 @@
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<div class="container">
<h1>Reactive Forms</h1>
<hero-list></hero-list>
</div>`
})
export class AppComponent { }

View File

@ -0,0 +1,39 @@
// #docplaster
// #docregion
// #docregion v1
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component'; // <-- #1 import component
// #enddocregion v1
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service'; // <-- #1 import service
// #docregion v1
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule // <-- #2 add to Angular module imports
],
declarations: [
AppComponent,
HeroDetailComponent, // <-- #3 declare app component
// #enddocregion v1
HeroListComponent
// #docregion v1
],
// #enddocregion v1
exports: [ // export for the DemoModule
AppComponent,
HeroDetailComponent,
HeroListComponent
],
providers: [ HeroService ], // <-- #4 provide HeroService
// #docregion v1
bootstrap: [ AppComponent ]
})
export class AppModule { }
// #enddocregion v1

View File

@ -0,0 +1,40 @@
// #docregion
// #docregion model-classes
export class Hero {
id = 0;
name = '';
addresses: Address[];
}
export class Address {
street = '';
city = '';
state = '';
zip = '';
}
// #enddocregion model-classes
export const heroes: Hero[] = [
{
id: 1,
name: 'Whirlwind',
addresses: [
{street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'},
{street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
]
},
{
id: 2,
name: 'Bombastic',
addresses: [
{street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'},
]
},
{
id: 3,
name: 'Magneta',
addresses: [ ]
},
];
export const states = ['CA', 'MD', 'OH', 'VA'];

View File

@ -0,0 +1,40 @@
<div class="container">
<h1>Reactive Forms</h1>
<h4><i>Pick a demo:</i>
<select [selectedIndex]="demo - 1" (change)="selectDemo($event.target.selectedIndex)">
<option *ngFor="let demo of demos">{{demo}}</option>
</select>
</h4>
<hr>
<div class="demo">
<hero-list *ngIf="demo===final"></hero-list>
<hero-detail-1 *ngIf="demo===1"></hero-detail-1>
<hero-detail-2 *ngIf="demo===2"></hero-detail-2>
<hero-detail-3 *ngIf="demo===3"></hero-detail-3>
<hero-detail-4 *ngIf="demo===4"></hero-detail-4>
<hero-detail-5 *ngIf="demo===5"></hero-detail-5>
<div *ngIf="demo >= 6 && demo !== final" >
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
<h3 *ngIf="!isLoading">Select a hero:</h3>
<nav>
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<hero-detail-6 [hero]=selectedHero *ngIf="demo===6"></hero-detail-6>
<hero-detail-7 [hero]=selectedHero *ngIf="demo===7"></hero-detail-7>
<hero-detail-8 [hero]=selectedHero *ngIf="demo===8"></hero-detail-8>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
/* tslint:disable:member-ordering */
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'demo.component.html'
})
export class DemoComponent {
demos: string[] = [
'Just a FormControl',
'FormControl in a FormGroup',
'Simple FormBuilder group',
'Group with multiple controls',
'Nested FormBuilder group',
'PatchValue',
'SetValue',
'FormArray',
'Final'].map(n => n + ' Demo');
final = this.demos.length;
demo = this.final; // current demo
heroes: Observable<Hero[]>;
isLoading = false;
selectedHero: Hero;
constructor(private heroService: HeroService) { }
getHeroes() {
this.isLoading = true;
this.heroes = this.heroService.getHeroes()
.finally(() => this.isLoading = false);
this.selectedHero = undefined;
}
select(hero: Hero) { this.selectedHero = hero; }
selectDemo(demo: number) {
this.demo = demo + 1;
this.getHeroes();
}
}

View File

@ -0,0 +1,33 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppModule } from './app.module';
import { DemoComponent } from './demo.component';
import { HeroDetailComponent1 } from './hero-detail-1.component';
import { HeroDetailComponent2 } from './hero-detail-2.component';
import { HeroDetailComponent3 } from './hero-detail-3.component';
import { HeroDetailComponent4 } from './hero-detail-4.component';
import { HeroDetailComponent5 } from './hero-detail-5.component';
import { HeroDetailComponent6 } from './hero-detail-6.component';
import { HeroDetailComponent7 } from './hero-detail-7.component';
import { HeroDetailComponent8 } from './hero-detail-8.component';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
AppModule,
],
declarations: [ DemoComponent,
HeroDetailComponent1,
HeroDetailComponent2,
HeroDetailComponent3,
HeroDetailComponent4,
HeroDetailComponent5,
HeroDetailComponent6,
HeroDetailComponent7,
HeroDetailComponent8],
bootstrap: [ DemoComponent ]
})
export class DemoModule { }

View File

@ -0,0 +1,8 @@
<!-- #docregion simple-control-->
<h2>Hero Detail</h2>
<h3><i>Just a FormControl</i></h3>
<label class="center-block">Name:
<input class="form-control" [formControl]="name">
</label>
<!-- #enddocregion simple-control-->

View File

@ -0,0 +1,15 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
// #enddocregion
@Component({
moduleId: module.id,
selector: 'hero-detail-1',
templateUrl: './hero-detail-1.component.html'
})
// #docregion v1
export class HeroDetailComponent1 {
name = new FormControl();
}

View File

@ -0,0 +1,18 @@
<!-- #docregion basic-form-->
<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<!-- #enddocregion basic-form-->
<!-- #docregion form-value-json -->
<p>Form value: {{ heroForm.value | json }}</p>
<!-- #enddocregion form-value-json -->

View File

@ -0,0 +1,18 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-2',
templateUrl: './hero-detail-2.component.html'
})
// #docregion v2
export class HeroDetailComponent2 {
heroForm = new FormGroup ({
name: new FormControl()
});
}
// #enddocregion v2

View File

@ -0,0 +1,16 @@
<!-- #docregion basic-form-->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with a single FormControl using FormBuilder</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<!-- #enddocregion basic-form-->
<!-- #docregion form-value-json -->
<p>Form value: {{ heroForm.value | json }}</p>
<p>Form status: {{ heroForm.status | json }}</p>
<!-- #enddocregion form-value-json -->

View File

@ -0,0 +1,28 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
// #docregion v3
export class HeroDetailComponent3 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
// #docregion required
this.heroForm = this.fb.group({
name: ['', Validators.required ],
});
// #enddocregion required
}
}
// #enddocregion v3

View File

@ -0,0 +1,26 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
// #docregion v3a
export class HeroDetailComponent3 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({
name: '', // <--- the FormControl called "name"
});
}
}
// #enddocregion v3a

View File

@ -0,0 +1,46 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with multiple FormControls</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -0,0 +1,35 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { states } from './data-model';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-4',
templateUrl: './hero-detail-4.component.html'
})
// #docregion v4
export class HeroDetailComponent4 {
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({
name: ['', Validators.required ],
street: '',
city: '',
state: '',
zip: '',
power: '',
sidekick: ''
});
}
}
// #enddocregion v4

View File

@ -0,0 +1,56 @@
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<!-- #docregion add-group-->
<div formGroupName="address" class="well well-lg">
<h4>Secret Lair</h4>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<!-- #enddocregion add-group-->
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Extra info for the curious:</h4>
<!-- #docregion inspect-value -->
<p>Name value: {{ heroForm.get('name').value }}</p>
<!-- #enddocregion inspect-value -->
<!-- #docregion inspect-child-control -->
<p>Street value: {{ heroForm.get('address.street').value}}</p>
<!-- #enddocregion inspect-child-control -->

View File

@ -0,0 +1,36 @@
/* tslint:disable:component-class-suffix */
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { states } from './data-model';
@Component({
moduleId: module.id,
selector: 'hero-detail-5',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v5
export class HeroDetailComponent5 {
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.heroForm = this.fb.group({ // <-- the parent FormGroup
name: ['', Validators.required ],
address: this.fb.group({ // <-- the child FormGroup
street: '',
city: '',
state: '',
zip: ''
}),
power: '',
sidekick: ''
});
}
}
// #enddocregion v5

View File

@ -0,0 +1,46 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>PatchValue to initialize a value</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -0,0 +1,59 @@
/* tslint:disable:component-class-suffix */
// #docregion import-input
import { Component, Input, OnChanges } from '@angular/core';
// #enddocregion import-input
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
// #docregion import-hero
import { Hero, states } from './data-model';
// #enddocregion import-hero
////////// 6 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-6',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v6
export class HeroDetailComponent6 implements OnChanges {
// #docregion hero
@Input() hero: Hero;
// #enddocregion hero
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
// #docregion hero-form-model
this.heroForm = this.fb.group({
name: ['', Validators.required ],
address: this.fb.group({
street: '',
city: '',
state: '',
zip: ''
}),
power: '',
sidekick: ''
});
// #enddocregion hero-form-model
}
// #docregion patch-value-on-changes
ngOnChanges() { // <-- wrap patchValue in ngOnChanges
this.heroForm.reset();
// #docregion patch-value
this.heroForm.patchValue({
name: this.hero.name
});
// #enddocregion patch-value
}
// #enddocregion patch-value-on-changes
}
// #enddocregion v6

View File

@ -0,0 +1,46 @@
<!-- #docregion -->
<h2>Hero Detail</h2>
<h3><i>A FormGroup with multiple FormControls</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ heroForm.value | json }}</p>

View File

@ -0,0 +1,67 @@
/* tslint:disable:component-class-suffix */
// #docplaster
// #docregion imports
import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Address, Hero, states } from './data-model';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-7',
templateUrl: './hero-detail-5.component.html'
})
// #docregion v7
export class HeroDetailComponent7 implements OnChanges {
@Input() hero: Hero;
heroForm: FormGroup;
states = states;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
// #docregion address-form-group
this.heroForm = this.fb.group({
name: ['', Validators.required ],
address: this.fb.group(new Address()), // <-- a FormGroup with a new address
power: '',
sidekick: ''
});
// #enddocregion address-form-group
}
// #docregion ngOnChanges
ngOnChanges() {
this.heroForm.reset({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
}
// #enddocregion ngOnChanges
/* First version of ngOnChanges
// #docregion ngOnChanges-1
ngOnChanges()
// #enddocregion ngOnChanges-1
*/
ngOnChanges1() {
// #docregion reset
this.heroForm.reset();
// #enddocregion reset
// #docregion ngOnChanges-1
// #docregion set-value
this.heroForm.setValue({
name: this.hero.name,
// #docregion set-value-address
address: this.hero.addresses[0] || new Address()
// #enddocregion set-value-address
});
// #enddocregion set-value
}
// #enddocregion ngOnChanges-1
}

View File

@ -0,0 +1,70 @@
<!-- #docplaster-->
<h3><i>Using FormArray to add groups</i></h3>
<form [formGroup]="heroForm" novalidate>
<p>Form Changed: {{ heroForm.dirty }}</p>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<!-- #docregion form-array-->
<!-- #docregion form-array-skeleton -->
<div formArrayName="secretLairs" class="well well-lg">
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
<!-- The repeated address template -->
<!-- #enddocregion form-array-skeleton -->
<h4>Address #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label class="center-block">City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label class="center-block">State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
<!-- #docregion form-array-skeleton -->
</div>
<!-- #enddocregion form-array-skeleton -->
<!-- #enddocregion form-array-->
<!-- #docregion add-lair -->
<button (click)="addLair()" type="button">Add a Secret Lair</button>
<!-- #enddocregion add-lair -->
<!-- #docregion form-array-->
<!-- #docregion form-array-skeleton -->
</div>
<!-- #enddocregion form-array-skeleton -->
<!-- #enddocregion form-array-->
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>

View File

@ -0,0 +1,69 @@
/* tslint:disable:component-class-suffix */
// #docregion imports
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Address, Hero, states } from './data-model';
// #enddocregion imports
@Component({
moduleId: module.id,
selector: 'hero-detail-8',
templateUrl: './hero-detail-8.component.html'
})
// #docregion v8
export class HeroDetailComponent8 implements OnChanges {
@Input() hero: Hero;
heroForm: FormGroup;
states = states;
// #docregion ctor
constructor(private fb: FormBuilder) {
this.createForm();
this.logNameChange();
}
// #enddocregion ctor
createForm() {
// #docregion secretLairs-form-array
this.heroForm = this.fb.group({
name: ['', Validators.required ],
secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray
power: '',
sidekick: ''
});
// #enddocregion secretLairs-form-array
}
logNameChange() {/* Coming soon */}
// #docregion onchanges
ngOnChanges() {
this.heroForm.reset({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
// #enddocregion onchanges
// #docregion get-secret-lairs
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
// #enddocregion get-secret-lairs
// #docregion set-addresses
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
// #enddocregion set-addresses
// #docregion add-lair
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
// #enddocregion add-lair
}

View File

@ -0,0 +1,73 @@
<!-- #docplaster -->
<!-- #docregion -->
<!-- #docregion buttons -->
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()" novalidate>
<div style="margin-bottom: 1em">
<button type="submit"
[disabled]="heroForm.pristine" class="btn btn-success">Save</button> &nbsp;
<button type="reset" (click)="revert()"
[disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
</div>
<!-- Hero Detail Controls -->
<!-- #enddocregion buttons -->
<div class="form-group">
<label>Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="secretLairs" class="well well-lg">
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label>Street:
<input class="form-control" formControlName="street">
</label>
</div>
<div class="form-group">
<label>City:
<input class="form-control" formControlName="city">
</label>
</div>
<div class="form-group">
<label>State:
<select class="form-control" formControlName="state">
<option *ngFor="let state of states" [value]="state">{{state}}</option>
</select>
</label>
</div>
<div class="form-group">
<label>Zip Code:
<input class="form-control" formControlName="zip">
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addLair()" type="button">Add a Secret Lair</button>
</div>
<!-- #docregion buttons -->
<div class="form-group">
<label>Super power:</label>
<label class="center-block"><input type="radio" formControlName="power" value="flight">Flight</label>
<label class="center-block"><input type="radio" formControlName="power" value="x-ray vision">X-ray vision</label>
<label class="center-block"><input type="radio" formControlName="power" value="strength">Strength</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<!-- #enddocregion buttons -->
<p>heroForm value: {{ heroForm.value | json}}</p>
<!-- #docregion name-change-log -->
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
<!-- #enddocregion name-change-log -->

View File

@ -0,0 +1,108 @@
// #docplaster
// #docregion
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
import { Address, Hero, states } from './data-model';
// #docregion import-service
import { HeroService } from './hero.service';
// #enddocregion import-service
// #docregion metadata
@Component({
moduleId: module.id,
selector: 'hero-detail',
templateUrl: './hero-detail.component.html'
})
// #enddocregion metadata
export class HeroDetailComponent implements OnChanges {
@Input() hero: Hero;
heroForm: FormGroup;
// #docregion log-name-change
nameChangeLog: string[] = [];
// #enddocregion log-name-change
states = states;
// #docregion ctor
constructor(
private fb: FormBuilder,
private heroService: HeroService) {
this.createForm();
this.logNameChange();
}
// #enddocregion ctor
createForm() {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([]),
power: '',
sidekick: ''
});
}
ngOnChanges() {
this.heroForm.reset({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
// #docregion on-submit
onSubmit() {
this.hero = this.prepareSaveHero();
this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.ngOnChanges();
}
// #enddocregion on-submit
// #docregion prepare-save-hero
prepareSaveHero(): Hero {
const formModel = this.heroForm.value;
// deep copy of form model lairs
const secretLairsDeepCopy: Address[] = formModel.secretLairs.map(
(address: Address) => Object.assign({}, address)
);
// return new `Hero` object containing a combination of original hero value(s)
// and deep copies of changed form model values
const saveHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
// addresses: formModel.secretLairs // <-- bad!
addresses: secretLairsDeepCopy
};
return saveHero;
}
// #enddocregion prepare-save-hero
// #docregion revert
revert() { this.ngOnChanges(); }
// #enddocregion revert
// #docregion log-name-change
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
(value: string) => this.nameChangeLog.push(value)
);
}
// #enddocregion log-name-change
}

View File

@ -0,0 +1,8 @@
<!-- #docregion -->
<nav>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<hero-detail [hero]="selectedHero"></hero-detail>
</div>

View File

@ -0,0 +1,17 @@
<!-- #docregion -->
<h3 *ngIf="isLoading"><i>Loading heroes ... </i></h3>
<h3 *ngIf="!isLoading">Select a hero:</h3>
<nav>
<button (click)="getHeroes()" class="btn btn-primary">Refresh</button>
<a *ngFor="let hero of heroes | async" (click)="select(hero)">{{hero.name}}</a>
</nav>
<div *ngIf="selectedHero">
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<!-- #docregion hero-binding -->
<hero-detail [hero]="selectedHero"></hero-detail>
<!-- #enddocregion hero-binding -->
</div>

View File

@ -0,0 +1,32 @@
// #docregion
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'hero-list.component.html'
})
export class HeroListComponent implements OnInit {
heroes: Observable<Hero[]>;
isLoading = false;
selectedHero: Hero;
constructor(private heroService: HeroService) { }
ngOnInit() { this.getHeroes(); }
getHeroes() {
this.isLoading = true;
this.heroes = this.heroService.getHeroes()
// Todo: error handling
.finally(() => this.isLoading = false);
this.selectedHero = undefined;
}
select(hero: Hero) { this.selectedHero = hero; }
}

View File

@ -0,0 +1,26 @@
// #docregion
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/delay';
import { Hero, heroes } from './data-model';
@Injectable()
export class HeroService {
delayMs = 500;
// Fake server get; assume nothing can go wrong
getHeroes(): Observable<Hero[]> {
return of(heroes).delay(this.delayMs); // simulate latency with delay
}
// Fake server update; assume nothing can go wrong
updateHero(hero: Hero): Observable<Hero> {
const oldHero = heroes.find(h => h.id === hero.id);
const newHero = Object.assign(oldHero, hero); // Demo: mutate cached hero
return of(newHero).delay(this.delayMs); // simulate latency with delay
}
}

View File

@ -0,0 +1,5 @@
// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,6 @@
// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module'; // just the final version
import { DemoModule } from './demo.module'; // demo picker
platformBrowserDynamic().bootstrapModule(DemoModule); // (AppModule);

View File

@ -0,0 +1,20 @@
{
"description": "Angular Reactive Forms (final)",
"files":[
"styles.css",
"app/app.component.ts",
"app/app.module.ts",
"app/data-model.ts",
"app/hero.service.ts",
"app/hero-detail.component.html",
"app/hero-detail.component.ts",
"app/hero-list.component.html",
"app/hero-list.component.ts",
"app/main-final.ts",
"index-final.html"
],
"main": "index-final.html",
"tags": ["reactive", "forms"]
}

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Hero Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<!-- #enddocregion bootstrap -->
<!-- Polyfills for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app/main-final').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Hero Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<!-- #enddocregion bootstrap -->
<!-- Polyfills for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,14 @@
{
"description": "Angular Reactive Forms (Demo runner)",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!app/app.component.1.ts",
"!app/hero-list.component.1.html",
"!app/main-final.ts",
"!index-final.html"
],
"tags": ["reactive", "forms"]
}

View File

@ -138,6 +138,11 @@
"intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree."
},
"reactive-forms": {
"title": "Reactive Forms",
"intro": "Create a reactive form using FormBuilder, groups, and arrays."
},
"server-communication": {
"title": "HTTP Client",
"intro": "Use an HTTP Client to talk to a remote server."

View File

@ -5,6 +5,14 @@ block includes
The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes.
## NEW: Reactive Forms guide (2017-01-31)
The new [**Reactive Forms**](reactive-forms.html) guide explains how and why to build a "reactive form".
"Reactive Forms" are the code-based counterpart to the declarative "Template Driven" forms approach
introduced in the [Forms](forms.html) guide.
Check it out before you decide how to add forms to your app.
Remember also that you can use both techniques in the same app,
choosing the approach that best fits each scenario.
## NEW: Deployment guide (2017-01-30)
The new [Deployment](deployment.html) guide describes techniques for putting your application on a server.
It includes important advice on optimizing for production.

View File

@ -668,7 +668,7 @@ figure.image-display
.file hero-form.component.html
.file hero-form.component.ts
.file main.ts
.file node_modules ...
.file node_modules ...
.file index.html
.file package.json
.file tsconfig.json

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,948 @@
<html lang="en"><head></head><body><form id="mainForm" method="post" action="https://embed.plnkr.co?show=preview" target="_self"><input type="hidden" name="entries[app/app.component.ts][content]" value="import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<div class=&quot;container&quot;>
<h1>Reactive Forms</h1>
<hero-list></hero-list>
</div>`
})
export class AppComponent { }
/*
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
*/"><input type="hidden" name="entries[app/app.component.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/app.module.ts][content]" value="import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component'; // <-- #1 import component
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service'; // <-- #1 import service
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule // <-- #2 add to Angular module imports
],
declarations: [
AppComponent,
HeroDetailComponent, // <-- #3 declare app component
HeroListComponent
],
providers: [ HeroService ], // <-- #4 provide HeroService
bootstrap: [ AppComponent ]
})
export class AppModule { }
/*
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
*/"><input type="hidden" name="entries[app/app.module.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/data-model.ts][content]" value="export class Hero {
id = 0;
name = '';
addresses: Address[];
}
export class Address {
street = '';
city = '';
state = '';
zip = '';
}
export const heroes: Hero[] = [
{
id: 1,
name: 'Whirlwind',
addresses: [
{street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'},
{street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
]
},
{
id: 2,
name: 'Bombastic',
addresses: [
{street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'},
]
},
{
id: 3,
name: 'Magneta',
addresses: [ ]
},
];
export const states = ['CA', 'MD', 'OH', 'VA'];
/*
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
*/"><input type="hidden" name="entries[app/data-model.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/demo.component.ts][content]" value="/* tslint:disable:member-ordering */
import { Component } from '@angular/core';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'demo.component.html'
})
export class DemoComponent {
demos: string[] = [
'Simple FormControl',
'Simple FormBuilder group',
'Group with multiple controls',
'Nested FormBuilder group',
'PatchValue',
'SetValue',
'FormArray',
'Final'].map(n => n + ' Demo');
final = this.demos.length;
demo = this.final; // current demo
heroes: Hero[];
selectedHero: Hero;
constructor(private heroService: HeroService) { }
getHeroes() {
return this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
select(hero: Hero) { this.selectedHero = hero; }
selectDemo(demo: number) {
this.demo = demo + 1;
this.heroes = undefined;
this.selectedHero = undefined;
this.getHeroes();
}
}
/*
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
*/"><input type="hidden" name="entries[app/demo.component.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/demo.module.ts][content]" value="import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import the module
import { DemoComponent } from './demo.component';
import { components } from './hero-detail-versions.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
declarations: [
DemoComponent, HeroDetailComponent, HeroListComponent,
...components
],
providers: [ HeroService ],
bootstrap: [ DemoComponent ]
})
export class DemoModule { }
/*
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
*/"><input type="hidden" name="entries[app/demo.module.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-versions.component.ts][content]" value="/* tslint:disable:component-class-suffix */
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Address, Hero, states } from './data-model';
//////// 1 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-1',
templateUrl: './hero-detail-1.component.html'
})
export class HeroDetailComponent1 {
heroForm = new FormGroup ({
name: new FormControl()
});
}
//////// 2 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-2',
templateUrl: './hero-detail-2.component.html'
})
export class HeroDetailComponent2 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.heroForm = this.fb.group({ // <--- make this.form a FormBuilder group
name: '', // <--- the FormControl called &quot;name&quot;
});
}
}
//////// 3 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
export class HeroDetailComponent3 {
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
street: '',
city: '',
state: '',
zip: ''
});
}
}
////////// 4 /////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-4',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent4 {
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({ // <-- the parent FormGroup
name: '',
address: this.fb.group({ // <-- the child FormGroup
street: '',
city: '',
state: '',
zip: ''
})
});
}
}
////////// 5 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-5',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent5 implements OnChanges { // <-- implements OnChanges
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
address: this.fb.group({
street: '',
city: '',
state: '',
zip: ''
})
});
}
ngOnChanges() { // <-- wrap patchValue in ngOnChanges
this.heroForm.patchValue({
name: this.hero.name
});
}
}
///////// 6 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-6',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent6 implements OnChanges {
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
address: this.fb.group(new Address()) // <-- a FormGroup with a new address
});
}
ngOnChanges() {
this.heroForm.reset();
this.heroForm.setValue({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
}
}
////////// 7 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-7',
templateUrl: './hero-detail-5.component.html'
})
export class HeroDetailComponent7 implements OnChanges {
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([]) // <-- secretLairs as an empty FormArray
});
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
ngOnChanges() {
this.heroForm.reset();
this.heroForm.patchValue({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
}
////////////////////////////////
export const components = [
HeroDetailComponent1,
HeroDetailComponent2,
HeroDetailComponent3,
HeroDetailComponent4,
HeroDetailComponent5,
HeroDetailComponent6,
HeroDetailComponent7
];
/*
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
*/"><input type="hidden" name="entries[app/hero-detail-versions.component.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail.component.ts][content]" value="// tslint:disable:no-unused-variable
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Address, Hero, states } from './data-model';
@Component({
moduleId: module.id,
selector: 'hero-detail',
templateUrl: './hero-detail.component.html'
})
export class HeroDetailComponent implements OnChanges {
@Input() hero: Hero;
@Output() save = new EventEmitter<Hero>();
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([])
});
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
ngOnChanges() {
this.heroForm.reset();
this.heroForm.patchValue({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
onSubmit() {
const formModel = this.heroForm.value;
const newHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
addresses: formModel.secretLairs
};
this.save.emit(newHero); // let parent decide what to do
console.log(newHero); // diagnostic
}
revert() { this.ngOnChanges(); }
}
/*
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
*/"><input type="hidden" name="entries[app/hero-detail.component.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-list.component.ts][content]" value="import { Component, OnInit } from '@angular/core';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'hero-list.component.html'
})
export class HeroListComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(private heroService: HeroService) { }
ngOnInit() { this.getHeroes(); }
getHeroes() {
this.heroService.getHeroes()
.then(heroes => this.heroes = heroes);
}
select(hero: Hero) { this.selectedHero = hero; }
onSave(hero: Hero) {
this.heroService.updateHero(hero)
.then(() => {
this.getHeroes();
const newHero = this.heroes.find(h => h === hero);
this.select(newHero);
});
}
}
/*
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
*/"><input type="hidden" name="entries[app/hero-list.component.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/hero.service.ts][content]" value="import { Injectable } from '@angular/core';
import { Hero, heroes } from './data-model';
@Injectable()
export class HeroService {
getHeroes(): Promise<Hero[]> {
return new Promise<Hero[]>(resolve => {
// simulate server latency with delay
setTimeout(() => resolve(heroes), 500);
});
}
updateHero(hero: Hero): Promise<Hero> {
// Demo: faking server update; nothing can go wrong ;-)
return new Promise<Hero>(resolve => {
// simulate server latency with delay
const ix = heroes.findIndex(h => h.id === hero.id);
setTimeout(() => {
heroes[ix] = hero;
resolve(hero);
}, 500);
});
}
}
/*
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
*/"><input type="hidden" name="entries[app/hero.service.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/main-final.ts][content]" value="// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
/*
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
*/"><input type="hidden" name="entries[app/main-final.ts][encoding]" value="utf8"><input type="hidden" name="entries[app/main.ts][content]" value="// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module'; // just the final version
import { DemoModule } from './demo.module'; // demo picker
platformBrowserDynamic().bootstrapModule(DemoModule); // (AppModule);
/*
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
*/"><input type="hidden" name="entries[app/main.ts][encoding]" value="utf8"><input type="hidden" name="entries[styles.css][content]" value="/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* items class */
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected {
background-color: #CFD8DC;
color: white;
}
.items li.selected:hover {
background-color: #BBD8DC;
}
.items .text {
position: relative;
top: -3px;
}
.items .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
/*
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
*/"><input type="hidden" name="entries[styles.css][encoding]" value="utf8"><input type="hidden" name="entries[app/demo.component.html][content]" value="<div class=&quot;container&quot;>
<h1>Reactive Forms</h1>
<h4><i>Pick a demo:</i>
<select [selectedIndex]=&quot;demo - 1&quot; (change)=&quot;selectDemo($event.target.selectedIndex)&quot;>
<option *ngFor=&quot;let demo of demos&quot;>{{demo}}</option>
</select>
</h4>
<hr>
<div class=&quot;demo&quot;>
<hero-list *ngIf=&quot;demo===final&quot;></hero-list>
<hero-detail-1 *ngIf=&quot;demo===1&quot;></hero-detail-1>
<hero-detail-2 *ngIf=&quot;demo===2&quot;></hero-detail-2>
<hero-detail-3 *ngIf=&quot;demo===3&quot;></hero-detail-3>
<hero-detail-4 *ngIf=&quot;demo===4&quot;></hero-detail-4>
<div *ngIf=&quot;demo >= 5 &amp;&amp; demo !== final&quot; >
<p *ngIf=&quot;!heroes&quot;><b><i>Loading heroes ... </i></b></p>
<h3 *ngIf=&quot;heroes&quot;>Select a hero:</h3>
<nav>
<a *ngFor=&quot;let hero of heroes&quot; (click)=&quot;select(hero)&quot;>{{hero.name}}</a>
</nav>
<div *ngIf=&quot;selectedHero&quot;>
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<hero-detail-5 [hero]=selectedHero *ngIf=&quot;demo===5&quot;></hero-detail-5>
<hero-detail-6 [hero]=selectedHero *ngIf=&quot;demo===6&quot;></hero-detail-6>
<hero-detail-7 [hero]=selectedHero *ngIf=&quot;demo===7&quot;></hero-detail-7>
</div>
</div>
</div>
</div>
<!--
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
-->"><input type="hidden" name="entries[app/demo.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-1.component.html][content]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with a single FormControl</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
</form>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail-1.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-2.component.html][content]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with a single FormControl using FormBuilder</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
</form>
<p>Form: {{ heroForm.value | json }}</p>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail-2.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-3.component.html][content]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with multiple FormControls in a single FormBuilder group</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</form>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail-3.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-4.component.html][content]" value="<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formGroupName=&quot;address&quot; class=&quot;well well-lg&quot;>
<h4>Secret Lair</h4>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Extra info for the curious:</h4>
<p>Name value: {{ heroForm.get('name').value }}</p>
<p>Street value: {{ heroForm.get('address.street').value}}</p>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail-4.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail-5.component.html][content]" value="<h3><i>Using FormArray to add groups</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot; (ngSubmit)=&quot;onSubmit(heroForm)&quot;>
<p>Form Changed: {{ heroForm.dirty }}</p>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formArrayName=&quot;secretLairs&quot; class=&quot;well well-lg&quot;>
<div *ngFor=&quot;let address of secretLairs.controls; let i=index&quot; [formGroupName]=&quot;i&quot; >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style=&quot;margin-left: 1em;&quot;>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)=&quot;addLair()&quot;>Add a Secret Lair</button> <!-- add button -->
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail-5.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-detail.component.html][content]" value="<form novalidate [formGroup]=&quot;heroForm&quot; (ngSubmit)=&quot;onSubmit(heroForm)&quot;>
<div style=&quot;margin-bottom: 1em&quot;>
<button type=&quot;submit&quot;
[disabled]=&quot;!heroForm.dirty&quot; class=&quot;btn btn-success&quot;>Save</button> &amp;nbsp;
<button type=&quot;reset&quot; (click)=&quot;revert()&quot;
[disabled]=&quot;!heroForm.dirty&quot; class=&quot;btn btn-danger&quot;>Revert</button>
</div>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formArrayName=&quot;secretLairs&quot; class=&quot;well well-lg&quot;>
<div *ngFor=&quot;let address of secretLairs.controls; let i=index&quot; [formGroupName]=&quot;i&quot; >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style=&quot;margin-left: 1em;&quot;>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)=&quot;addLair()&quot;>Add a Secret Lair</button> <!-- add button -->
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<!--
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
-->"><input type="hidden" name="entries[app/hero-detail.component.html][encoding]" value="utf8"><input type="hidden" name="entries[app/hero-list.component.html][content]" value="<p *ngIf=&quot;!heroes&quot;><b><i>Loading heroes ... </i></b></p>
<h3 *ngIf=&quot;heroes&quot;>Select a hero:</h3>
<nav>
<a *ngFor=&quot;let hero of heroes&quot; (click)=&quot;select(hero)&quot;>{{hero.name}}</a>
</nav>
<div *ngIf=&quot;selectedHero&quot;>
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<hero-detail [hero]=&quot;selectedHero&quot; (save)=&quot;onSave($event)&quot;></hero-detail>
</div>
<!--
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
-->"><input type="hidden" name="entries[app/hero-list.component.html][encoding]" value="utf8"><input type="hidden" name="entries[index.html][content]" value="<!DOCTYPE html>
<html>
<head>
<title>Hero Form</title>
<meta charset=&quot;UTF-8&quot;>
<meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;>
<link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;>
<link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css&quot;>
<!-- Polyfills for older browsers -->
<script src=&quot;https://unpkg.com/core-js/client/shim.min.js&quot;></script>
<script src=&quot;https://unpkg.com/zone.js@0.7.2?main=browser&quot;></script>
<script src=&quot;https://unpkg.com/reflect-metadata@0.1.8&quot;></script>
<script src=&quot;https://unpkg.com/systemjs@0.19.39/dist/system.src.js&quot;></script>
<script src=&quot;https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js&quot;></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
<!--
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
-->"><input type="hidden" name="entries[index.html][encoding]" value="utf8"><input type="hidden" name="tags[0]" value="angular"><input type="hidden" name="tags[1]" value="example"><input type="hidden" name="tags[2]" value="reactive"><input type="hidden" name="tags[3]" value="forms"><input type="hidden" name="title" value="Angular Example - Reactive Forms (final)"><input type="hidden" name="source[type]" value="Reactive Forms (final)"><input type="hidden" name="source[url]" value="https://angular.io"></form><script>document.getElementById("mainForm").submit();</script></body></html>

View File

@ -0,0 +1,948 @@
<html lang="en"><head></head><body><form id="mainForm" method="post" action="http://plnkr.co/edit/?p=preview" target="_self"><input type="hidden" name="files[app/app.component.ts]" value="import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<div class=&quot;container&quot;>
<h1>Reactive Forms</h1>
<hero-list></hero-list>
</div>`
})
export class AppComponent { }
/*
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
*/"><input type="hidden" name="files[app/app.module.ts]" value="import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component'; // <-- #1 import component
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service'; // <-- #1 import service
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule // <-- #2 add to Angular module imports
],
declarations: [
AppComponent,
HeroDetailComponent, // <-- #3 declare app component
HeroListComponent
],
providers: [ HeroService ], // <-- #4 provide HeroService
bootstrap: [ AppComponent ]
})
export class AppModule { }
/*
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
*/"><input type="hidden" name="files[app/data-model.ts]" value="export class Hero {
id = 0;
name = '';
addresses: Address[];
}
export class Address {
street = '';
city = '';
state = '';
zip = '';
}
export const heroes: Hero[] = [
{
id: 1,
name: 'Whirlwind',
addresses: [
{street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'},
{street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
]
},
{
id: 2,
name: 'Bombastic',
addresses: [
{street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'},
]
},
{
id: 3,
name: 'Magneta',
addresses: [ ]
},
];
export const states = ['CA', 'MD', 'OH', 'VA'];
/*
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
*/"><input type="hidden" name="files[app/demo.component.ts]" value="/* tslint:disable:member-ordering */
import { Component } from '@angular/core';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'demo.component.html'
})
export class DemoComponent {
demos: string[] = [
'Simple FormControl',
'Simple FormBuilder group',
'Group with multiple controls',
'Nested FormBuilder group',
'PatchValue',
'SetValue',
'FormArray',
'Final'].map(n => n + ' Demo');
final = this.demos.length;
demo = this.final; // current demo
heroes: Hero[];
selectedHero: Hero;
constructor(private heroService: HeroService) { }
getHeroes() {
return this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
select(hero: Hero) { this.selectedHero = hero; }
selectDemo(demo: number) {
this.demo = demo + 1;
this.heroes = undefined;
this.selectedHero = undefined;
this.getHeroes();
}
}
/*
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
*/"><input type="hidden" name="files[app/demo.module.ts]" value="import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import the module
import { DemoComponent } from './demo.component';
import { components } from './hero-detail-versions.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from './hero.service';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule
],
declarations: [
DemoComponent, HeroDetailComponent, HeroListComponent,
...components
],
providers: [ HeroService ],
bootstrap: [ DemoComponent ]
})
export class DemoModule { }
/*
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
*/"><input type="hidden" name="files[app/hero-detail-versions.component.ts]" value="/* tslint:disable:component-class-suffix */
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Address, Hero, states } from './data-model';
//////// 1 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-1',
templateUrl: './hero-detail-1.component.html'
})
export class HeroDetailComponent1 {
heroForm = new FormGroup ({
name: new FormControl()
});
}
//////// 2 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-2',
templateUrl: './hero-detail-2.component.html'
})
export class HeroDetailComponent2 {
heroForm: FormGroup; // <--- heroForm is of type FormGroup
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.heroForm = this.fb.group({ // <--- make this.form a FormBuilder group
name: '', // <--- the FormControl called &quot;name&quot;
});
}
}
//////// 3 ////////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-3',
templateUrl: './hero-detail-3.component.html'
})
export class HeroDetailComponent3 {
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
street: '',
city: '',
state: '',
zip: ''
});
}
}
////////// 4 /////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-4',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent4 {
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({ // <-- the parent FormGroup
name: '',
address: this.fb.group({ // <-- the child FormGroup
street: '',
city: '',
state: '',
zip: ''
})
});
}
}
////////// 5 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-5',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent5 implements OnChanges { // <-- implements OnChanges
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
address: this.fb.group({
street: '',
city: '',
state: '',
zip: ''
})
});
}
ngOnChanges() { // <-- wrap patchValue in ngOnChanges
this.heroForm.patchValue({
name: this.hero.name
});
}
}
///////// 6 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-6',
templateUrl: './hero-detail-4.component.html'
})
export class HeroDetailComponent6 implements OnChanges {
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
address: this.fb.group(new Address()) // <-- a FormGroup with a new address
});
}
ngOnChanges() {
this.heroForm.reset();
this.heroForm.setValue({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
}
}
////////// 7 ////////////////////
@Component({
moduleId: module.id,
selector: 'hero-detail-7',
templateUrl: './hero-detail-5.component.html'
})
export class HeroDetailComponent7 implements OnChanges {
@Input() hero: Hero;
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([]) // <-- secretLairs as an empty FormArray
});
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
ngOnChanges() {
this.heroForm.reset();
this.heroForm.patchValue({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
}
////////////////////////////////
export const components = [
HeroDetailComponent1,
HeroDetailComponent2,
HeroDetailComponent3,
HeroDetailComponent4,
HeroDetailComponent5,
HeroDetailComponent6,
HeroDetailComponent7
];
/*
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
*/"><input type="hidden" name="files[app/hero-detail.component.ts]" value="// tslint:disable:no-unused-variable
import { Component, EventEmitter, Input, Output, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Address, Hero, states } from './data-model';
@Component({
moduleId: module.id,
selector: 'hero-detail',
templateUrl: './hero-detail.component.html'
})
export class HeroDetailComponent implements OnChanges {
@Input() hero: Hero;
@Output() save = new EventEmitter<Hero>();
states = states;
heroForm: FormGroup;
constructor(private fb: FormBuilder) {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([])
});
}
get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};
ngOnChanges() {
this.heroForm.reset();
this.heroForm.patchValue({
name: this.hero.name
});
this.setAddresses(this.hero.addresses);
}
setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
onSubmit() {
const formModel = this.heroForm.value;
const newHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
addresses: formModel.secretLairs
};
this.save.emit(newHero); // let parent decide what to do
console.log(newHero); // diagnostic
}
revert() { this.ngOnChanges(); }
}
/*
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
*/"><input type="hidden" name="files[app/hero-list.component.ts]" value="import { Component, OnInit } from '@angular/core';
import { Hero } from './data-model';
import { HeroService } from './hero.service';
@Component({
moduleId: module.id,
selector: 'hero-list',
templateUrl: 'hero-list.component.html'
})
export class HeroListComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(private heroService: HeroService) { }
ngOnInit() { this.getHeroes(); }
getHeroes() {
this.heroService.getHeroes()
.then(heroes => this.heroes = heroes);
}
select(hero: Hero) { this.selectedHero = hero; }
onSave(hero: Hero) {
this.heroService.updateHero(hero)
.then(() => {
this.getHeroes();
const newHero = this.heroes.find(h => h === hero);
this.select(newHero);
});
}
}
/*
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
*/"><input type="hidden" name="files[app/hero.service.ts]" value="import { Injectable } from '@angular/core';
import { Hero, heroes } from './data-model';
@Injectable()
export class HeroService {
getHeroes(): Promise<Hero[]> {
return new Promise<Hero[]>(resolve => {
// simulate server latency with delay
setTimeout(() => resolve(heroes), 500);
});
}
updateHero(hero: Hero): Promise<Hero> {
// Demo: faking server update; nothing can go wrong ;-)
return new Promise<Hero>(resolve => {
// simulate server latency with delay
const ix = heroes.findIndex(h => h.id === hero.id);
setTimeout(() => {
heroes[ix] = hero;
resolve(hero);
}, 500);
});
}
}
/*
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
*/"><input type="hidden" name="files[app/main-final.ts]" value="// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
/*
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
*/"><input type="hidden" name="files[app/main.ts]" value="// tslint:disable:no-unused-variable
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module'; // just the final version
import { DemoModule } from './demo.module'; // demo picker
platformBrowserDynamic().bootstrapModule(DemoModule); // (AppModule);
/*
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
*/"><input type="hidden" name="files[styles.css]" value="/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* items class */
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected {
background-color: #CFD8DC;
color: white;
}
.items li.selected:hover {
background-color: #BBD8DC;
}
.items .text {
position: relative;
top: -3px;
}
.items .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
/*
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
*/"><input type="hidden" name="files[app/demo.component.html]" value="<div class=&quot;container&quot;>
<h1>Reactive Forms</h1>
<h4><i>Pick a demo:</i>
<select [selectedIndex]=&quot;demo - 1&quot; (change)=&quot;selectDemo($event.target.selectedIndex)&quot;>
<option *ngFor=&quot;let demo of demos&quot;>{{demo}}</option>
</select>
</h4>
<hr>
<div class=&quot;demo&quot;>
<hero-list *ngIf=&quot;demo===final&quot;></hero-list>
<hero-detail-1 *ngIf=&quot;demo===1&quot;></hero-detail-1>
<hero-detail-2 *ngIf=&quot;demo===2&quot;></hero-detail-2>
<hero-detail-3 *ngIf=&quot;demo===3&quot;></hero-detail-3>
<hero-detail-4 *ngIf=&quot;demo===4&quot;></hero-detail-4>
<div *ngIf=&quot;demo >= 5 &amp;&amp; demo !== final&quot; >
<p *ngIf=&quot;!heroes&quot;><b><i>Loading heroes ... </i></b></p>
<h3 *ngIf=&quot;heroes&quot;>Select a hero:</h3>
<nav>
<a *ngFor=&quot;let hero of heroes&quot; (click)=&quot;select(hero)&quot;>{{hero.name}}</a>
</nav>
<div *ngIf=&quot;selectedHero&quot;>
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<hero-detail-5 [hero]=selectedHero *ngIf=&quot;demo===5&quot;></hero-detail-5>
<hero-detail-6 [hero]=selectedHero *ngIf=&quot;demo===6&quot;></hero-detail-6>
<hero-detail-7 [hero]=selectedHero *ngIf=&quot;demo===7&quot;></hero-detail-7>
</div>
</div>
</div>
</div>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail-1.component.html]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with a single FormControl</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
</form>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail-2.component.html]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with a single FormControl using FormBuilder</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
</form>
<p>Form: {{ heroForm.value | json }}</p>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail-3.component.html]" value="<h2>Hero Detail</h2>
<h3><i>A simple form with multiple FormControls in a single FormBuilder group</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</form>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail-4.component.html]" value="<form novalidate [formGroup]=&quot;heroForm&quot;>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formGroupName=&quot;address&quot; class=&quot;well well-lg&quot;>
<h4>Secret Lair</h4>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Extra info for the curious:</h4>
<p>Name value: {{ heroForm.get('name').value }}</p>
<p>Street value: {{ heroForm.get('address.street').value}}</p>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail-5.component.html]" value="<h3><i>Using FormArray to add groups</i></h3>
<form novalidate [formGroup]=&quot;heroForm&quot; (ngSubmit)=&quot;onSubmit(heroForm)&quot;>
<p>Form Changed: {{ heroForm.dirty }}</p>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formArrayName=&quot;secretLairs&quot; class=&quot;well well-lg&quot;>
<div *ngFor=&quot;let address of secretLairs.controls; let i=index&quot; [formGroupName]=&quot;i&quot; >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style=&quot;margin-left: 1em;&quot;>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)=&quot;addLair()&quot;>Add a Secret Lair</button> <!-- add button -->
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<!--
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
-->"><input type="hidden" name="files[app/hero-detail.component.html]" value="<form novalidate [formGroup]=&quot;heroForm&quot; (ngSubmit)=&quot;onSubmit(heroForm)&quot;>
<div style=&quot;margin-bottom: 1em&quot;>
<button type=&quot;submit&quot;
[disabled]=&quot;!heroForm.dirty&quot; class=&quot;btn btn-success&quot;>Save</button> &amp;nbsp;
<button type=&quot;reset&quot; (click)=&quot;revert()&quot;
[disabled]=&quot;!heroForm.dirty&quot; class=&quot;btn btn-danger&quot;>Revert</button>
</div>
<div class=&quot;form-group&quot;>
<label>Name:</label>
<input class=&quot;form-control&quot; formControlName=&quot;name&quot;>
</div>
<div formArrayName=&quot;secretLairs&quot; class=&quot;well well-lg&quot;>
<div *ngFor=&quot;let address of secretLairs.controls; let i=index&quot; [formGroupName]=&quot;i&quot; >
<!-- The repeated address template -->
<h4>Address #{{i + 1}}</h4>
<div style=&quot;margin-left: 1em;&quot;>
<div class=&quot;form-group&quot;>
<label>Street:</label>
<input class=&quot;form-control&quot; formControlName=&quot;street&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>City:</label>
<input class=&quot;form-control&quot; formControlName=&quot;city&quot;>
</div>
<div class=&quot;form-group&quot;>
<label>State:</label>
<select class=&quot;form-control&quot; formControlName=&quot;state&quot;>
<option *ngFor=&quot;let state of states&quot;>{{state}}</option>
</select>
</div>
<div class=&quot;form-group&quot;>
<label>Zip Code:</label>
<input class=&quot;form-control&quot; formControlName=&quot;zip&quot;>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)=&quot;addLair()&quot;>Add a Secret Lair</button> <!-- add button -->
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<!--
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
-->"><input type="hidden" name="files[app/hero-list.component.html]" value="<p *ngIf=&quot;!heroes&quot;><b><i>Loading heroes ... </i></b></p>
<h3 *ngIf=&quot;heroes&quot;>Select a hero:</h3>
<nav>
<a *ngFor=&quot;let hero of heroes&quot; (click)=&quot;select(hero)&quot;>{{hero.name}}</a>
</nav>
<div *ngIf=&quot;selectedHero&quot;>
<hr>
<h2>Hero Detail</h2>
<h3>Editing: {{selectedHero.name}}</h3>
<hero-detail [hero]=&quot;selectedHero&quot; (save)=&quot;onSave($event)&quot;></hero-detail>
</div>
<!--
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
-->"><input type="hidden" name="files[index.html]" value="<!DOCTYPE html>
<html>
<head>
<title>Hero Form</title>
<meta charset=&quot;UTF-8&quot;>
<meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;>
<link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;>
<link rel=&quot;stylesheet&quot; href=&quot;https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css&quot;>
<!-- Polyfills for older browsers -->
<script src=&quot;https://unpkg.com/core-js/client/shim.min.js&quot;></script>
<script src=&quot;https://unpkg.com/zone.js@0.7.2?main=browser&quot;></script>
<script src=&quot;https://unpkg.com/reflect-metadata@0.1.8&quot;></script>
<script src=&quot;https://unpkg.com/systemjs@0.19.39/dist/system.src.js&quot;></script>
<script src=&quot;https://cdn.rawgit.com/angular/angular.io/b3c65a9/public/docs/_examples/_boilerplate/systemjs.config.web.js&quot;></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
<!--
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
-->"><input type="hidden" name="tags[0]" value="angular"><input type="hidden" name="tags[1]" value="example"><input type="hidden" name="tags[2]" value="reactive"><input type="hidden" name="tags[3]" value="forms"><input type="hidden" name="private" value="true"><input type="hidden" name="description" value="Angular Example - Reactive Forms (final)"></form><script>document.getElementById("mainForm").submit();</script></body></html>