项目结构

一 首页 ( index.html )
1<!doctype html> 2<html lang="en"> 3<head> 4 <meta charset="utf-8"> 5 <title>Angular4ReactiveForm</title> 6 <base href="/"> 7 8 <meta name="viewport" content="width=device-width, initial-scale=1"> 9 <link rel="icon" type="image/x-icon" href="favicon.ico"> 10</head> 11<body> 12 <app-hero-list></app-hero-list> 13</body> 14</html>
二 根模块 ( app.module.ts )
1import { BrowserModule } from '@angular/platform-browser'; 2import { ReactiveFormsModule } from '@angular/forms'; 3import { NgModule } from '@angular/core'; 4 5 6import { HeroListComponent } from './hero-list/hero-list.component'; 7import { HeroDetailComponent } from './hero-detail/hero-detail.component'; 8import { HeroService } from './hero.service'; 9 10 11@NgModule({ 12 declarations: [ 13 HeroListComponent, 14 HeroDetailComponent 15 ], 16 imports: [ 17 BrowserModule, 18 ReactiveFormsModule 19 ], 20 providers: [HeroService], 21 bootstrap: [HeroListComponent] 22}) 23export class AppModule { }
三 列表脚本 ( hero-list.component.ts )
1import { Component, OnInit } from '@angular/core'; 2import { Observable } from 'rxjs/Observable'; 3import { finalize } from 'rxjs/operators'; 4import { Hero } from '../model/model'; 5import { HeroService } from '../hero.service'; 6 7@Component({ 8 selector: 'app-hero-list', 9 templateUrl: './hero-list.component.html', 10 styleUrls: ['./hero-list.component.css'] 11}) 12export class HeroListComponent implements OnInit { 13 isLoading = false; 14 heroes: Observable<Hero[]>; 15 selectedHero: Hero; 16 constructor(public heroService: HeroService) { } 17 18 ngOnInit() { 19 } 20 21 /** 22 * 获取Hero列表 23 * 24 * @memberof HeroListComponent 25 */ 26 getHeroes() { 27 this.isLoading = true; 28 this.heroes = this.heroService.getHeroes() 29 .pipe(finalize(() => this.isLoading = false)); 30 this.selectedHero = null; 31 } 32 33 /** 34 * 选择Hero 35 * 36 * @param {Hero} hero 37 * @memberof HeroListComponent 38 */ 39 select(hero: Hero) { 40 this.selectedHero = hero; 41 } 42 43}
四 列表模版 ( hero-list.component.html )
1<h3 *ngIf="isLoading"> 2 <i>Loading heroes ... </i> 3</h3> 4<h3 *ngIf="!isLoading"> 5 <i>Select a hero</i> 6</h3> 7<nav> 8 <button (click)="getHeroes();" class="btn btn-primary">Refresh</button> 9 <a *ngFor="let hero of heroes | async" (click)="select(hero);">{{hero.name}}</a> 10</nav> 11<div *ngIf="selectedHero"> 12 <hr/> 13 <h2>Hero Detail</h2> 14 <h3>Editing:{{selectedHero.name}}</h3> 15 <app-hero-detail [hero]="selectedHero"></app-hero-detail> 16</div>
五 详情脚本 ( hero-detail.component.ts )
1import { Component, OnInit, Input, OnChanges, OnDestroy } from '@angular/core'; 2import { Hero, Address } from '../model/model'; 3import { FormBuilder, FormGroup, FormArray, AbstractControl, FormControl } from '@angular/forms'; 4import { HeroService } from '../hero.service'; 5import { provinces } from '../model/model'; 6 7@Component({ 8 selector: 'app-hero-detail', 9 templateUrl: './hero-detail.component.html', 10 styleUrls: ['./hero-detail.component.css'] 11}) 12export class HeroDetailComponent implements OnInit, OnChanges, OnDestroy { 13 @Input() hero: Hero; 14 heroForm: FormGroup; 15 provinces: string[] = provinces; 16 nameChangeLog: string[] = []; 17 constructor(private fb: FormBuilder, private heroService: HeroService) { 18 this.createForm(); 19 this.logNameChanges(); 20 } 21 22 /** 23 * 24 * getter方法:从而可以直接访问secretLairs 25 * @readonly 26 * @type {FormArray} 27 * @memberof HeroDetailComponent 28 */ 29 get secretLairs(): FormArray { 30 return <FormArray>this.heroForm.get('secretLairs'); 31 } 32 33 ngOnInit() { // 单击Hero按钮,选择Hero时执行 34 console.log('详情页面初始化'); 35 } 36 37 ngOnDestroy(): void { // 单击Refresh按钮,重新获取Hero列表时执行 38 console.log('详情页面销毁'); 39 } 40 41 ngOnChanges() { 42 this.rebuildForm(); 43 } 44 45 createForm() { 46 this.heroForm = this.fb.group({ 47 name: '', 48 secretLairs: this.fb.array([]), 49 power: '', 50 sidekick: '' 51 }); 52 } 53 54 /** 55 * 56 * 选择英雄、还原表单时重置表单 57 * @memberof HeroDetailComponent 58 */ 59 rebuildForm() { 60 this.heroForm.reset({ // 将字段标记为pristine、untouched 61 name: this.hero.name 62 }); 63 this.setAddress(this.hero.addresses); 64 } 65 66 /** 67 * 68 * 设置表单的地址 69 * @param {Address[]} addresses 70 * @memberof HeroDetailComponent 71 */ 72 setAddress(addresses: Address[]) { 73 const addressFormGroups = addresses.map(address => this.fb.group(address)); 74 const addressForArray = this.fb.array(addressFormGroups); 75 this.heroForm.setControl('secretLairs', addressForArray); 76 } 77 /** 78 * 新增一个地址 79 * 80 * @memberof HeroDetailComponent 81 */ 82 addLair() { 83 this.secretLairs.push(this.fb.group(new Address())); 84 } 85 86 /** 87 * 保存表单 88 * 89 * @memberof HeroDetailComponent 90 */ 91 save() { 92 this.hero = this.prepareCopyHero(); 93 this.heroService.updateHero(this.hero).subscribe( 94 (val) => { // 成功 95 96 }, 97 (err) => { // 出错 98 99 }); 100 this.rebuildForm(); 101 } 102 103 /** 104 * 深度复制Hero对象 105 * 106 * @returns {Hero} 107 * @memberof HeroDetailComponent 108 */ 109 prepareCopyHero(): Hero { 110 const formModel: any = this.heroForm.value; // AbstractControl是FormGroup、FormArray、FormControl的基类 111 const secrectLairDeepCopy: Address[] = formModel.secretLairs.map( 112 (address: Address) => Object.assign({}, address) 113 ); 114 const savedHero: Hero = { 115 id: this.hero.id, 116 name: formModel.name, 117 addresses: secrectLairDeepCopy 118 }; 119 return savedHero; 120 } 121 122 /** 123 * 还原表单 124 * 125 * @memberof HeroDetailComponent 126 */ 127 revert() { 128 this.rebuildForm(); 129 } 130 131 /** 132 * 订阅valueChanges属性( Observale对象 ),监控详情页面名称的变化,选择英雄、输入名称时执行 133 * 134 * @memberof HeroDetailComponent 135 */ 136 logNameChanges() { 137 const nameControl: FormControl = <FormControl>this.heroForm.get('name'); 138 nameControl.valueChanges.forEach((val: string) => this.nameChangeLog.push(val)); 139 } 140}
六 详情模版 ( hero-detail.component.html )
1<form [formGroup]='heroForm'> 2 <!-- 按钮 --> 3 <div style="margin-bottom: 1em;"> 4 <button type="button" (click)="save();" [disabled]="heroForm.pristine" class="btn btn-success">Save</button> 5 <button type="button" (click)="revert();" [disabled]="heroForm.pristine" class="btn btn-success">Revert</button> 6 </div> 7 <!-- 名称 --> 8 <div class="form-group"> 9 <label class="center-block">Name: 10 <input class="form-control" formControlName="name" /> 11 </label> 12 </div> 13 <!-- 地址循环开始 --> 14 <div formArrayName="secretLairs" class="well well-lg"> 15 <div *ngFor="let address of secretLairs.controls;let i = index;" [formGroupName]="i"> 16 <h4>Address #{{i+1}}</h4> 17 <div style="margin-left: 1em;"> 18 <div class="form-group"> 19 <label class="center-block">Street: 20 <input class="form-control" formControlName="street" /> 21 </label> 22 </div> 23 <div class="form-group"> 24 <label class="center-block">City: 25 <input class="form-control" formControlName="city" /> 26 </label> 27 </div> 28 <div class="form-group"> 29 <label class="center-block">Province: 30 <select class="form-control" formControlName="province"> 31 <option *ngFor="let province of provinces" [value]="province">{{province}}</option> 32 </select> 33 </label> 34 </div> 35 <div class="form-group"> 36 <label class="center-block">Zip Code: 37 <input class="form-control" formControlName="zip" /> 38 </label> 39 </div> 40 </div> 41 </div> 42 <button (click)="addLair();" type="button">Add a Secret Lair</button> 43 </div> 44 <!-- 地址循环结束 --> 45</form> 46 47<p>heroForm value: {{heroForm.value | json}}</p> 48 49<h4>Name change log</h4> 50<ul> 51 <li *ngFor="let name of nameChangeLog">{{name}}</li> 52</ul>
七 服务脚本 ( hero.service.ts )
1import { Injectable } from '@angular/core'; 2import { of } from 'rxjs/observable/of'; 3import { delay } from 'rxjs/operators'; 4import { Hero, heroes } from './model/model'; 5import { Observable } from 'rxjs/Observable'; 6 7@Injectable() 8export class HeroService { 9 10 delayMs = 500; 11 12 constructor() { } 13 14 /** 15 * 获取Hero对象列表 16 * 17 * @returns {Observable<Hero[]>} 18 * @memberof HeroService 19 */ 20 getHeroes(): Observable<Hero[]> { 21 return of(heroes).pipe(delay(this.delayMs)); 22 } 23 24 25 /** 26 * 更新Hero对象 27 * 28 * @param {Hero} hero 29 * @returns {Observable<Hero>} 30 * @memberof HeroService 31 */ 32 updateHero(hero: Hero): Observable<Hero> { 33 const oldHero = heroes.find(h => h.id === hero.id); 34 const newHero = Object.assign(oldHero, hero); // 潜复制 35 return of(newHero).pipe(delay(this.delayMs)); 36 } 37}
八 数据模型 ( model.ts )
1export class Hero { 2 constructor(public id: number, public name: string, public addresses: Address[]) { 3 4 } 5} 6 7export class Address { 8 constructor(public province?: string, public city?: string, public street?: string, public zip?: number) { 9 10 } 11} 12export const heroes: Hero[] = [ 13 new Hero(1, 'Whirlwind', [ 14 new Address('山东', '青岛', '东海路', 266000), 15 new Address('江苏', '苏州', '干将路', 215000) 16 ]), 17 new Hero(2, 'Bombastic', [ 18 new Address('福建', '厦门', '环岛路', 361000) 19 ]), 20 new Hero(3, 'Magneta', []) 21]; 22 23export const provinces: string[] = ['山东', '江苏', '福建', '四川'];