Angular之响应式表单 ( Reactive Forms )

项目结构

一 首页 ( 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[] = ['山东', '江苏', '福建', '四川'];

 

posted on 2018-04-25 17:22  沙滩海风  阅读(1237)  评论(0编辑  收藏  举报

导航