项目结构
一 首页 ( index.html )
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular4ReactiveForm</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-hero-list></app-hero-list>
</body>
</html>
二 根模块 ( app.module.ts )
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroService } from './hero.service';
@NgModule({
declarations: [
HeroListComponent,
HeroDetailComponent
],
imports: [
BrowserModule,
ReactiveFormsModule
],
providers: [HeroService],
bootstrap: [HeroListComponent]
})
export class AppModule { }
三 列表脚本 ( hero-list.component.ts )
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { finalize } from 'rxjs/operators';
import { Hero } from '../model/model';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
isLoading = false;
heroes: Observable<Hero[]>;
selectedHero: Hero;
constructor(public heroService: HeroService) { }
ngOnInit() {
}
/**
* 获取Hero列表
*
* @memberof HeroListComponent
*/
getHeroes() {
this.isLoading = true;
this.heroes = this.heroService.getHeroes()
.pipe(finalize(() => this.isLoading = false));
this.selectedHero = null;
}
/**
* 选择Hero
*
* @param {Hero} hero
* @memberof HeroListComponent
*/
select(hero: Hero) {
this.selectedHero = hero;
}
}
四 列表模版 ( hero-list.component.html )
<h3 *ngIf="isLoading">
<i>Loading heroes ... </i>
</h3>
<h3 *ngIf="!isLoading">
<i>Select a hero</i>
</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>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
</div>
五 详情脚本 ( hero-detail.component.ts )
import { Component, OnInit, Input, OnChanges, OnDestroy } from '@angular/core';
import { Hero, Address } from '../model/model';
import { FormBuilder, FormGroup, FormArray, AbstractControl, FormControl } from '@angular/forms';
import { HeroService } from '../hero.service';
import { provinces } from '../model/model';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit, OnChanges, OnDestroy {
@Input() hero: Hero;
heroForm: FormGroup;
provinces: string[] = provinces;
nameChangeLog: string[] = [];
constructor(private fb: FormBuilder, private heroService: HeroService) {
this.createForm();
this.logNameChanges();
}
/**
*
* getter方法:从而可以直接访问secretLairs
* @readonly
* @type {FormArray}
* @memberof HeroDetailComponent
*/
get secretLairs(): FormArray {
return <FormArray>this.heroForm.get('secretLairs');
}
ngOnInit() { // 单击Hero按钮,选择Hero时执行
console.log('详情页面初始化');
}
ngOnDestroy(): void { // 单击Refresh按钮,重新获取Hero列表时执行
console.log('详情页面销毁');
}
ngOnChanges() {
this.rebuildForm();
}
createForm() {
this.heroForm = this.fb.group({
name: '',
secretLairs: this.fb.array([]),
power: '',
sidekick: ''
});
}
/**
*
* 选择英雄、还原表单时重置表单
* @memberof HeroDetailComponent
*/
rebuildForm() {
this.heroForm.reset({ // 将字段标记为pristine、untouched
name: this.hero.name
});
this.setAddress(this.hero.addresses);
}
/**
*
* 设置表单的地址
* @param {Address[]} addresses
* @memberof HeroDetailComponent
*/
setAddress(addresses: Address[]) {
const addressFormGroups = addresses.map(address => this.fb.group(address));
const addressForArray = this.fb.array(addressFormGroups);
this.heroForm.setControl('secretLairs', addressForArray);
}
/**
* 新增一个地址
*
* @memberof HeroDetailComponent
*/
addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}
/**
* 保存表单
*
* @memberof HeroDetailComponent
*/
save() {
this.hero = this.prepareCopyHero();
this.heroService.updateHero(this.hero).subscribe(
(val) => { // 成功
},
(err) => { // 出错
});
this.rebuildForm();
}
/**
* 深度复制Hero对象
*
* @returns {Hero}
* @memberof HeroDetailComponent
*/
prepareCopyHero(): Hero {
const formModel: any = this.heroForm.value; // AbstractControl是FormGroup、FormArray、FormControl的基类
const secrectLairDeepCopy: Address[] = formModel.secretLairs.map(
(address: Address) => Object.assign({}, address)
);
const savedHero: Hero = {
id: this.hero.id,
name: formModel.name,
addresses: secrectLairDeepCopy
};
return savedHero;
}
/**
* 还原表单
*
* @memberof HeroDetailComponent
*/
revert() {
this.rebuildForm();
}
/**
* 订阅valueChanges属性( Observale对象 ),监控详情页面名称的变化,选择英雄、输入名称时执行
*
* @memberof HeroDetailComponent
*/
logNameChanges() {
const nameControl: FormControl = <FormControl>this.heroForm.get('name');
nameControl.valueChanges.forEach((val: string) => this.nameChangeLog.push(val));
}
}
六 详情模版 ( hero-detail.component.html )
<form [formGroup]='heroForm'>
<!-- 按钮 -->
<div style="margin-bottom: 1em;">
<button type="button" (click)="save();" [disabled]="heroForm.pristine" class="btn btn-success">Save</button>
<button type="button" (click)="revert();" [disabled]="heroForm.pristine" class="btn btn-success">Revert</button>
</div>
<!-- 名称 -->
<div class="form-group">
<label class="center-block">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">
<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">Province:
<select class="form-control" formControlName="province">
<option *ngFor="let province of provinces" [value]="province">{{province}}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Zip Code:
<input class="form-control" formControlName="zip" />
</label>
</div>
</div>
</div>
<button (click)="addLair();" type="button">Add a Secret Lair</button>
</div>
<!-- 地址循环结束 -->
</form>
<p>heroForm value: {{heroForm.value | json}}</p>
<h4>Name change log</h4>
<ul>
<li *ngFor="let name of nameChangeLog">{{name}}</li>
</ul>
七 服务脚本 ( hero.service.ts )
import { Injectable } from '@angular/core';
import { of } from 'rxjs/observable/of';
import { delay } from 'rxjs/operators';
import { Hero, heroes } from './model/model';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class HeroService {
delayMs = 500;
constructor() { }
/**
* 获取Hero对象列表
*
* @returns {Observable<Hero[]>}
* @memberof HeroService
*/
getHeroes(): Observable<Hero[]> {
return of(heroes).pipe(delay(this.delayMs));
}
/**
* 更新Hero对象
*
* @param {Hero} hero
* @returns {Observable<Hero>}
* @memberof HeroService
*/
updateHero(hero: Hero): Observable<Hero> {
const oldHero = heroes.find(h => h.id === hero.id);
const newHero = Object.assign(oldHero, hero); // 潜复制
return of(newHero).pipe(delay(this.delayMs));
}
}
八 数据模型 ( model.ts )
export class Hero {
constructor(public id: number, public name: string, public addresses: Address[]) {
}
}
export class Address {
constructor(public province?: string, public city?: string, public street?: string, public zip?: number) {
}
}
export const heroes: Hero[] = [
new Hero(1, 'Whirlwind', [
new Address('山东', '青岛', '东海路', 266000),
new Address('江苏', '苏州', '干将路', 215000)
]),
new Hero(2, 'Bombastic', [
new Address('福建', '厦门', '环岛路', 361000)
]),
new Hero(3, 'Magneta', [])
];
export const provinces: string[] = ['山东', '江苏', '福建', '四川'];