Angular 深入浅出三十天:表单与测试 Day29 - ControlContainer

Day29

昨天跟大家分享了自订表单元件的作法,但昨天的作法只适用於一个栏位、一个 FormControl

虽然 FormControl 里是可以设 {} 的值,但如果我们真的想要的是一个可以直接用 [formGroup][formArray] 所使用的元件呢?

没问题,只要你想要, Angular 都给你

实作开始

大家还记得之前我们做了个「被保人表单」吧?

一开始只有「姓名」、「性别」跟「年龄」这三个栏位,後来我们加了「联络资讯」的栏位,这次我们再帮它加个「联络地址」的栏位吧!

一般来说,联络地址的栏位通常会分成「县市」、「乡镇市区」、「邮递区号」与「地址」,而「县市」、「乡镇市区」与「邮递区号」之间会有一些连动逻辑,县市」与「乡镇市区」这两个栏位也通常会是下拉选单,其他的则是一般的 input 栏位。

首先一样先把 HTML 准备好,像这样:

<p><label>联络地址:</label></p>
<p>
  <select>
    <option value="">请选择县市</option>
  </select>
  <select>
    <option value="">请选择乡镇市区</option>
  </select>
</p>
<p>
  <input type="text" style="width: 4rem" placeholder="邮递区号">
  <input type="text" place="请输入地址">
</p>

画面:

Template View

样式用 inline 的方式设定是方便教学,小朋友们要尽量少用噢!

接着,在 .ts 里加入地址的相关栏位的 FormGroupFormControl

const addressInfoFormGroup = this.formBuilder.group({
  city: '',
  district: '',
  zip: '',
  address: ''
});

return this.formBuilder.group({
  name: [
    '',
    [Validators.required, Validators.minLength(2), Validators.maxLength(10)]
  ],
  gender: ['', Validators.required],
  age: ['', Validators.required],
  contactInfoType: contactInfoTypeControl,
  contactInfo: contactInfoControl,
  addressInfo: addressInfoFormGroup
});

然後再将其绑与画面上元素绑定,像这样:

<ng-container formGroupName="addressInfo">
  <p><label>联络地址:</label></p>
  <p>
    <select formControlName="city">
      <option value="">请选择县市</option>
    </select>
    <select formControlName="district">
      <option value="">请选择乡镇市区</option>
    </select>
  </p>
  <p>
    <input type="text" style="width: 4rem" placeholder="邮递区号" formControlName="zip">
    <input type="text" placeholder="请输入地址" formControlName="address">
  </p>
</ng-container>

连动逻辑的实作就交给大家练习罗,我们今天没有要着重於此部分的处理。

至此,我们就完成了第一步的准备工作。

ControlContainer

接下来,我们就要将联络地址这块拆成一个独立的 Component ─ AddressInfoComponent

首先,先将 HTML 搬过去并稍微调整一下:

<ng-container [formGroup]="formGroup">
  <p>
    <select formControlName="city">
      <option value="">请选择县市</option>
    </select>
    <select formControlName="district">
      <option value="">请选择乡镇市区</option>
    </select>
  </p>
  <p>
    <input type="text" style="width: 4rem" placeholder="邮递区号" formControlName="zip">
    <input type="text" placeholder="请输入地址" formControlName="address">
  </p>
</ng-container>

接着在 AddressInfoComponent 里注入 ControlContainer

export class AddressInfoComponent {

  constructor(private controlContainer: ControlContainer) { }

}

然後加上:

get formGroup(): FormGroup {
  return this.controlContainer.control as FormGroup;
}

再回到被保人表单里,把原本的联络地址区块改成:

<p><label>联络地址:</label></p>
<app-address-info formGroupName="addressInfo"></app-address-info>

至此就大功告成了!是不是超简单的?!

不过之所以这麽简单是因为这是 Reactive Forms 的方式,今天的 ControlContainer 不像昨天的 ControlValueAccessor 可以做一次之後,两种方式都可以使用。

如果今天这个元件是要让 Template Driven Forms 使用的话,首先要先将 Template 原本用 Reactive Forms 的绑定方式改成使用 Template Driven Forms 的绑定方式,像是这样:

<ng-container ngModelGroup="addressInfo">
  <p>
    <select name="zip" ngModel>
      <option value="">请选择县市</option>
    </select>
    <select name="district" >
      <option value="">请选择乡镇市区</option>
    </select>
  </p>
  <p>
    <input type="text" style="width: 4rem" placeholder="邮递区号" name="zip" ngModel>
    <input type="text" placeholder="请输入地址" name="address" ngModel>
  </p>
</ng-container>

然後也不用在 AddressInfoComponent 里注入 ControlContainer ,而是改在 AddressInfoComponentMetaDataviewProviders 里新增以下设定:

@Component({
  selector: 'app-address-info',
  templateUrl: './address-info.component.html',
  styleUrls: ['./address-info.component.scss'],
  viewProviders:[
    {
      provide: ControlContainer, 
      useExisting: NgForm
    }
  ]
})
export class AddressInfoComponent {

这样就能直接用 <app-address-info></app-address-info> 的方式使用这个元件了。

大家觉得,是 Reactive Forms 的方式好用,还是 Template Driven Forms 的方式好用呢?

本日小结

今天的重点主要是让大家知道要怎麽使用 ControlContainer 这个类别来包装我们的元件,以达到提昇重用性维护性的目的。

虽然麻烦的是,它没办法像昨天分享的 ControlValueAccessor 一样,做好了之後可以适用於 Template Driven FormsReactive Forms ,但好在它的用法其实颇为简单,主要的差异就只有在 Template Driven Forms 需要靠 viewProvider ,而 Reactive Froms 只要注入就行。

关於 viewProvider 与 provider 的差异,我推荐大家可以去看 Kevin (台湾 Angular GDE)的 [Angular] viewProviders V.S. providers ,我觉得写得非常的清楚。

此外,如果觉得我分享不好,也可以参考 Kevin 的 [Angular] ControlContainer 的应用

今天的程序码会放在 Github - Branch: day29 上供大家参考,建议大家在看我的实作之前,先按照需求规格自己做一遍,之後再跟我的对照,看看自己的实作跟我的实作不同的地方在哪里、有什麽好处与坏处,如此反覆咀嚼消化後,我相信你一定可以进步地非常快!

如果有任何的问题或是回馈,还请麻烦留言给我让我知道!


<<:  29 - 有效的使用 Observability 的资料 (3) - 资料的生命周期管理

>>:  【Day 29】- 应对反爬虫技术-综合篇

[Day 20] 两段式训练比两段式左转更安全 (迁移学习技巧)

前言 走过了资料分析、演算法选择後, 我们得知了有些可以改善模型的方向: 解决资料不平衡(Done)...

各式各样的演算法 - Greedy、Dynamic Programming 与 Divide and Conquer

题组回顾与观念统整 这一段我们着重在「动态规划」优化,如何从穷举或递回的方法中进一步地将结果记录下...

【Day17】期间限定:函式的参数

函式会将参数传入函式里面,让它们成为函式里的变数,让程序码去做运算。参数只能在函式里刷存在感(期间...

应用 LINE Front-end Framework 轻松建立互动 (2)

今天我们要来研究 line-liff-v2-starter 里面写了些什麽,这对之後想开发自己的 L...

网路是怎样连接的(十一) 初探IP协议

思考重点 封包是如何找到下一个端节点的 IP地址在封包转发中扮演的角色 MAC地址在封包转发中扮演的...