在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先生的日子!