网站首页 » 前端开发 » Angular 2+ » Angular 2+ 响应式表单
上一篇:
下一篇:

Angular 2+ 响应式表单

前言

使用响应式表单,我们可以在组件中创建表单控件的对象树,响应式表单可以让使用响应式编程模式、测试和校验变得更容易。

来看看演示效果:

Angular 2+ 响应式表单

Angular 2+ 响应式表单之旅

app.component.html
<site-list></site-list>
app.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  ngOnInit() { };
}
site-list.component.html
<nav class="navbar navbar-inverse">
  <div class="container text-center">
    <ul class="nav navbar-nav">
      <li *ngFor="let site of sites | async" (click)="select(site)">
        <a href="javascript:;">{{site.name}}</a>
      </li>
    </ul>
  </div>
</nav>
<div *ngIf="selectedSite">
  <site-detail [site]="selectedSite"></site-detail>
</div>
site-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';

import { Site } from './site-mock';
import { SiteServices } from './site.services';

@Component({
    selector: 'site-list',
    templateUrl: './site-list.component.html'
})

export class SiteListComponent implements OnInit {
    sites: Observable<Site[]>;

    selectedSite: Site;

    constructor(private _siteServices: SiteServices) { }

    ngOnInit() { this.getSites(); }

    select(selectedSite: Site) {
        this.selectedSite = selectedSite;
    }

    getSites() {
        this.sites = this._siteServices.getSites();
        this.selectedSite = undefined;
    }
}
site-detail.component.html
<div class="container">
  <div class="text-right">
    <button type="button" class="btn btn-danger" (click)="clear(siteForm)">Clear</button>
    <button type="button" class="btn btn-danger" [disabled]="siteForm.pristine" (click)="revert(siteForm)">Revert</button>
    <button type="button" class="btn btn-primary" (click)="AddAddress()">AddAddress</button>
  </div>
  <div>
    <h1>My Form</h1>
    <form [formGroup]="siteForm" (ngSubmit)="onSubmit()" novalidate>
      <div class="form-group">
        <label for="name">Name</label>
        <input class="form-control" formControlName="name">
      </div>
      <div class="form-group">
        <label for="domain">Domain</label>
        <input class="form-control" formControlName="domain">
      </div>
      <h3>Addresses</h3>
      <div formArrayName="secretLairs">
        <div *ngFor="let address of secretLairs.controls;let i = index;" [formGroupName]="i">
          <div class="panel panel-default">
            <div class="panel-heading">
              <h3 class="panel-title">地址:{{i+1}}</h3>
            </div>
            <div class="panel-body">
              <div class="form-group">
                <label for="street">Street</label>
                <input class="form-control" formControlName="street">
              </div>
              <div class="form-group">
                <label for="city">City</label>
                <input class="form-control" formControlName="city">
              </div>
              <div class="form-group">
                <label for="province">Province</label>
                <input class="form-control" formControlName="province">
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="form-group">
        <label class="center-block">Type</label>
        <select class="form-control" formControlName="type">
          <option *ngFor="let type of types" [value]="type">{{type}}</option>
        </select>
      </div>
      <button type="submit" class="btn btn-primary">Save</button>
    </form>
  </div>
</div>

下面的代码才是本文的重点

site-detail.component.ts
import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
import { Site, TYPES, Address } from './site-mock';
import { SiteServices } from './site.services';

@Component({
    selector: 'site-detail',
    templateUrl: './site-detail.component.html'
})

export class SiteDetailComponent implements OnChanges {
    // 接收传进来的值(site)
    @Input() site: Site;
    siteForm: FormGroup;
    types = TYPES;
    constructor(
        private fb: FormBuilder,
        private _siteServices: SiteServices
    ) {
        this.creatForm();
    }

    // 初始化数据
    ngOnChanges() {
        // 初始化详情页数据
        this.siteForm.reset({
            name: this.site.name,
            domain: this.site.domain,
            type: this.site.type
        });
        this.setAddresses(this.site.addresses);
    }

    // 创建表单树
    creatForm() {
        // FormBuilder.group是一个用来创建FormGroup的工厂方法,它接受一个对象,对象的键和值分别是FormControl的名字和它的定义。
        this.siteForm = this.fb.group({
            name: ['', Validators.required],
            domain: ['', Validators.required],
            type: ['', Validators.required],
            secretLairs: this.fb.array([])
        });
    }

    // FormArray 对 FormGroup 进行分组,就像 FormGroup 对 FormControl 进行分组一样
    setAddresses(addresses: Address[]) {
        const addressFGs = addresses.map(address => this.fb.group(address)); // 匿名函数的简写方式,会返回函数中的执行结果,不需要显式添加 return
        const addressFormArray = this.fb.array(addressFGs);
        this.siteForm.setControl('secretLairs', addressFormArray);
    }

    // 不写下面这个函数,模板中会找不到 secretLairs
    get secretLairs(): FormArray {
        // 使用 FormGroup.get 方法来获取到 FormArray 的引用
        return this.siteForm.get('secretLairs') as FormArray;
    };

    // 重置表单的标识
    clear(form) {
        form.reset();
    }

    // 添加地址
    AddAddress() {
        this.secretLairs.push(this.fb.group(new Address()));
    }

    // 数据恢复
    revert() { this.ngOnChanges(); }

    // 构建一个用于保存的新对象
    prepareSaveSite(): Site {
        const formModel = this.siteForm.value;
        // deep copy of form model lairs
        const secretLairsDeepCopy: Address[] = formModel.secretLairs.map(
            (address: Address) => Object.assign({}, address)
        );

        // and deep copies of changed form model values
        const saveSite: Site = {
            id: this.site.id,
            name: formModel.name as string, // as string 声明为字符串类型
            domain: formModel.domain as string,
            type: formModel.type as string,
            // addresses: formModel.secretLairs // <-- bad!
            addresses: secretLairsDeepCopy
        };
        return saveSite;
    }

    // 提交数据
    onSubmit() {
        this.site = this.prepareSaveSite();
        this._siteServices.updateSite(this.site).subscribe(/* error handling */);
        this.ngOnChanges();
    }
}

为什么没有直接像上面注释的那样写,直接把 formModel.secretLairs 赋值给 addresses。首先们们得知道两个东西:

数据模型:从服务端传过来的原始数据。

表单模型:数据模型的复本,用于表单。

所以用户修改时,数据流是从 DOM 元素流向表单模型,而不是数据模型。表单控件永远不会修改数据模型。

现在来回答下上面那个问题。

如果我们直接 addresses: formModel.secretLairs , 当这个 site 对象数据保存到 SITES 后,site 中的 addresses 的地址就直接指向了表单的 formModel.secretLairs,也就是当你在表单中修改这个对象 addresses 对象中的值时同时也会修改这个 site 对象中的 addresses,于是就有了上面我们通过 prepareSaveSite 方法来组装一个新的对象。

建一个简单的服务(数据获取、更新)

site.services.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/delay';
import { Site, SITES } from './site-mock';

@Injectable()
export class SiteServices {
    delayMs = 500;
    getSites(): Observable<Site[]> {
        return of(SITES).delay(this.delayMs); // 模拟延迟
    }

    updateSite(site: Site): Observable<Site> {
        const oldSite = SITES.find(h => h.id === site.id);
        const newSite = Object.assign(oldSite, site);
        return of(newSite).delay(this.delayMs); // 模拟延迟
    }
}

创建类型及数据

site-mock.ts
export class Site {
    id = 0;
    name = '';
    domain = '';
    type = '';
    addresses: Address[];
}

export class Address {
    street = '';
    city = '';
    province = '';
}

export const TYPES = ['技术类', '生活类'];

export const SITES: Site[] = [
    {
        id: 1,
        name: '云库网',
        domain: 'http://yunkus.com',
        type: '技术类',
        addresses: [
            { street: '珠一路', city: '珠海', province: '广东省' },
            { street: '广一路', city: '广州', province: '广东省' }
        ]
    },
    {
        id: 2,
        name: '朝夕熊博客',
        domain: 'http://zhaoxixiong.com',
        type: '生活类',
        addresses: [
            { street: '深一路', city: '深圳', province: '广东省' }
        ]
    }
];

在 app.module.ts 中引入相关的资源(组件,服务)

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { SiteListComponent } from './site-list.component';
import { SiteDetailComponent } from './site-detail.component';
import { SiteServices } from './site.services';

@NgModule({
  declarations: [
    AppComponent,
    SiteListComponent,
    SiteDetailComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [SiteServices],
  bootstrap: [AppComponent]
})
export class AppModule { }

上例参照官方示例来写的,大多代码也基本差不多。自己过一遍也能理清一些东西。Angular 2+ 响应式表单就分享到这里

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/angular-core-knowledge-reactive-forms/

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END