在Reactive forms中,追求类型是否是错误的呢?

这篇文章是2019年Angular #2 Advent Calendar第17天的文章。

首先

在一个项目中,当我正在创建一个相当复杂的表单时,我系统地研究了响应式表单,并在其中感到了对响应式表单的类型需求,因此我会给它添加类型。

作为备忘录的同时,我将比较Angular的模板驱动表单的写法,并将其进行总结。

以庆祝Advent Calendar的开始,我希望能够仿效周围前辈们的文章,写出精彩的内容!

在Angular中实现表单

大家都曾经实施过登录表单、支付表单、会员注册表单等等。在Angular中,有两种实现表单的方法,并且它们都有各自的特点,所以我们需要考虑哪种方法适用于自己的项目。

模板驱动表单

特点

    • FormsModuleを使い、テンプレート側でディレクティブとともに定義されるので、テンプレート側がごちゃごちゃしがち

 

    • テンプレート側にもフォームの情報が記載されているため、フォームの構造をテンプレートとコンポーネントどちらも見なければならない

 

    • DOMを用意しないとテストが出来ない

 

    Array構造のフォームを作成できない

样品

<h1>Login Form - Template-driven Forms</h1>
<form #form="ngForm" class="container" (ngSubmit)="submitForm()">

  <div class="item">
    <label>ID: </label>
    <input type="text" name="id" [(ngModel)]="id" required>
  </div>

  <div class="item">
    <label>Mail: </label>
    <input type="text" name="mail" [(ngModel)]="mail" required>
  </div>

    <div class="item">
    <label>Password: </label>
    <input type="password" name="password" [(ngModel)]="password" required>
  </div>

  <button type="submit" [disabled]="!form.valid">Submit</button>

</form>
@Component({
  selector: 'app-template-driven-forms',
  templateUrl: './template-driven-forms.component.html',
  styleUrls: ['./template-driven-forms.component.css']
})
export class TemplateDrivenFormsComponent implements OnInit {

  id: number;
  mail: string;
  password: string;

  constructor() { }

  ngOnInit() {
    this.initForm();
  }

  submitForm() {
    console.log('submit');
    console.log(this.id);
    console.log(this.mail);
    console.log(this.password);
  }

  private initForm() {
    this.id = 0;
    this.mail = 'a';
    this.password = 'b';
  }

}

响应式表单

特点

    • ReactiveFormsModuleを使い、コンポーネント側で明示的に構造を記述するので、コンポーネント側がごちゃごちゃしがち

 

    • コンポーネント側にフォームの情報がすべて記載されるため、コンポーネント側を見ればフォームの構造がわかる

 

    • DOMを用意しなくてもテストができる

 

    値が変更された時の変化をsubscribeできる

範例 lì)

<h1>Login Form - Reactive Forms</h1>
<form class="container" [formGroup]="form" (ngSubmit)="submitForm()">

  <div class="item">
    <label>ID: </label>
    <input type="text" formControlName="id">
  </div>

  <div class="item">
    <label>Mail: </label>
    <input type="text" formControlName="mail">
  </div>

  <div class="item">
    <label>Password: </label>
    <input type="password" formControlName="password">
  </div>

  <button type="submit" [disabled]="!form.valid">Submit</button>

</form>
@Component({
  selector: 'app-reactive-forms',
  templateUrl: './reactive-forms.component.html',
  styleUrls: ['./reactive-forms.component.css']
})
export class ReactiveFormsComponent implements OnInit {

  form: FormGroup;

  constructor(
    private fb: FormBuilder,
  ) { }

  ngOnInit() {
    this.initForm();
  }

  submitForm() {
    console.log('submit');
    console.log(this.form.get('id'));
    console.log(this.form.get('mail'));
    console.log(this.form.get('password'));
  }

  private initForm() {
    this.form = this.fb.group({
      id: [0, Validators.required],
      mail: [''],
      password: ['a', Validators.required],
    });
  }

}

你要用哪一个?可以混合使用吗?

由于我平时觉得Reactive Forms的结构容易理解,测试也比较容易(虽然我不太会写),所以我选择采用Reactive Forms进行开发。

在中文中进行重述:
表单的混合使用是可能的,但出于以下原因,采用其中一种方式会更为稳妥。

    • チームメンバーの学習曲線が上がる

 

    FormsModule, ReactiveFormsModuleどちらのモジュールもロードしないといけないのでバンドルサイズが大きくなる

原本,基於模板驅動的表單設計方法是從AngularJS的外觀中繼承過來的,所以在從AngularJS遷移到Angular 2或者更高版本時,如果只使用簡單結構的表單,可能可以考慮使用模板驅動的形式。

然而,当编写上述代码时,您会发现在Angular响应式表单中无法为其添加类型。

如果在这种状态下进行开发,可能会不小心输入错误类型的值,或调用不存在的属性。

给它一个形状

如果要定义界面的话 dehuà)

在有关 Type-safe Form 的讨论中,dmorosinotto正在定义 ReactiveFormsModule 的扩展接口。
https://github.com/angular/angular/issues/13721#issuecomment-468745950

通过将其转换为自定义的model interface类型,您可以创建类型安全的表单。

樣本

将上述的接口组作为 TypedForms.d.ts 文件放置。

定义一个用于Form的接口,并从该接口定义中调用类型安全的 FormGroupTyped。

interface IFormType {
  id: number;
  mail: string;
  password: string;
}
  form: FormGroupTyped<IFormType>;

  private initForm() {
    this.form = this.fb.group({
      id: [0, Validators.required],
      mail: ['', Validators.required],
      password: ['', Validators.required],
    }) as FormGroupTyped<IFormType>;

    let num: number = this.form.value.id; // no Error
    let str: string = this.form.value.id; // [Error] Type 'number' is not assignable to type 'string'.
    this.form.get('id').setValue(0); // no Error
    this.form.get('id').setValue(true); // [Error] Argument of type '"a"' is not assignable to parameter of type 'never'.
  }

当使用 `form.value.id` 时,就可以自动补全并添加类型信息,因此在组件内操作时不再会出错!?

现在模板方面也可以实现补全功能!

顺便提一下,当写这篇文章的时候,我在拼写”password”时打错成了”passowrd”,在使用自动补全后才注意到,哈哈。

如果使用图书馆的情况下

如果你希望用更简单的方法解决,可以使用npm软件包。

从Star数来看,估计有人在使用KostyaTretyak/ng-stack和no0x9d/ngx-strongly-typed-forms。

这次我想尝试使用KostyaTretyak/ng-stack。

请使用以下命令安装库文件。

$ npm i -s @ng-stack/forms

按照README的指示,将模块导入到app.module.ts中。

import { NgStackFormsModule } from '@ng-stack/forms';

// ...

@NgModule({
  // ...
  imports: [
    NgStackFormsModule
  ]

// ...

});

只需要一个选项来用中文转述以下内容:
可以使用 @ng-stack/forms 来代替 @angular/forms 来实现类型安全的表单。

import { FormGroup, FormControl, FormArray } from '@ng-stack/forms';

@Component({
  selector: 'app-reactive-forms',
  templateUrl: './reactive-forms.component.html',
  styleUrls: ['./reactive-forms.component.css']
})
export class ReactiveFormsComponent implements OnInit {

  private initForm() {
    const formControl = new FormControl('some string');
    const value = formControl.value; // some string

    formControl.setValue(123); // Error: Argument of type '123' is not assignable...
  }

}

考虑到对Angular版本升级较为迟缓的情况,如果需要快速实现类型安全表单,也许可以考虑使用库。

如果等待标准实施

关于Angular的强类型安全的响应式表单,自古以来就一直存在着许多讨论和各种问题。

特别讨论正在进行的是 https://github.com/angular/angular/issues/13721,并且已基于该问题提出了建议 https://github.com/angular/angular/issues/31963。

未来不久将会标准化更加类型安全的表格。

总结

我开始接触Angular已经过了大约一年半的时间,我意识到它的强类型安全和开发便利性所带来的优势,即使是Angular的初学者也可以保持一定的质量并在学习中实现。

另外,除了 GitHub 上的讨论活跃外,这次的解决方案几乎也是从 GitHub 的讨论中得到的。所以,我希望能更好地理解 Angular 的理念、内部结构,以及英语,并参与到开源项目的贡献中去!

虽然以上的文章糟糕,但感谢您阅读到最后!!

明天是@fusho-takahashi先生的日子!

广告
将在 10 秒后关闭
bannerAds