Angular 14 严格类型反应式表单 - 如何使用现有接口键入 FormGroup 模型

haw*_*kus 34 strong-typing angular angular14

我只是在玩新的角度类型表单 api,不知道如何输入FormGroup在不声明特定“FormInterface”女巫必须与原始界面相匹配的情况下进行键入。

也许我错过了一些东西,或者根本不可能这样做。

我能够使事情正常工作(下面的示例),但我不喜欢这里的UserForm接口声明,它没有对User接口的引用,并且必须是它的镜像副本,但带有FormControl<>字段。

在这种情况下是否可以FormGroup仅使用User界面进行输入?

stackblitz上提供完整的工作示例

用户模型.ts

export interface User {
  firstName: string;
  lastName: string;
  email: string;
  age: number | null;
}
Run Code Online (Sandbox Code Playgroud)

用户服务.ts

@Injectable()
export class UserService {

  add(user: User): void {
    console.log('Add user', user);
  }

}
Run Code Online (Sandbox Code Playgroud)

应用程序组件.ts

interface UserForm {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  email: FormControl<string>;
  age: FormControl<number | null>;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
  form: FormGroup<UserForm>;

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.form = new FormGroup({
      firstName: new FormControl<string>('', { nonNullable: true }),
      lastName: new FormControl('', { nonNullable: true }),
      email: new FormControl('', { nonNullable: true }),
      age: new FormControl<number | null>(null),
    });
  }

  submitForm(): void {
    this.userService.add(this.form.getRawValue());
  }
}
Run Code Online (Sandbox Code Playgroud)

Rco*_*rNY 34

我也遇到了这个问题,因为我总是分配我的FormGroup稍后时间,并且我对必须在每个声明中添加大量样板类型感到不高兴FormGroup。感觉就像我在重复自己,所以我最终所做的是创建一个运行良好的实用程序类型。

export type ModelFormGroup<T> = FormGroup<{
  [K in keyof T]: FormControl<T[K]>;
}>;
Run Code Online (Sandbox Code Playgroud)

这使得我可以轻松地键入FormGroup我拥有的许多数据模型的声明。例如:

export interface UserModel {
  email: string;
  password: string;
}

// In a class...
userFormGroup: ModelFormGroup<UserModel>;

ngOnInit() {
  this.userFormGroup = this._formBuilder.nonNullable.group({
    email: [''],
    password: [''],
  });
}
Run Code Online (Sandbox Code Playgroud)

很多时候,我的数据模型拥有的属性比我希望表单拥有的属性多,因此我利用TypeScript 的实用程序类型 、、、Pick等等...RequiredOmit

export interface UserModel {
  email: string;
  password: string;
  dateAdded: Date;
  dateUpdated: Date;
}

// In a class...
userFormGroup: ModelFormGroup<Pick<UserModel, 'email' | 'password' >>;

ngOnInit() {
  this.userFormGroup = this._formBuilder.nonNullable.group({
    email: [''],
    password: [''],
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 对我来说,当我尝试在提交时发出值时,此解决方案会引发错误:“‘Partial&lt;{ username: string;password: string; }&gt;’类型的参数不可分配给‘UserModel’类型的参数。属性类型“用户名”不兼容。类型“字符串|未定义”不可分配给类型“字符串”。类型“未定义”不可分配给类型“字符串”。“ (2认同)
  • @balintd 这个解决方案与您所看到的无关。`FormGroup` **总是**返回一个 `Partial` bc 表单控件可能被禁用,因此返回 `undefined`。检查[此处的文档。](https://angular.io/guide/typed-forms#partial-values)如果您想访问包括禁用控件的值,从而绕过可能的未定义字段,您可以使用 `.getRawValue( )`。 (2认同)
  • 极好的!这帮助我解决了问题并进一步理解了 Typescript。 (2认同)

小智 6

我建议定义您的 FormGroup 并User从中推断接口。使用以下实用程序类型可以为您提供帮助。

export type FormValue<T extends AbstractControl> = T extends AbstractControl<infer TValue, any> ? TValue : never;
export type FormRawValue<T extends AbstractControl> = T extends AbstractControl<any, infer TRawValue> ? TRawValue : never;
Run Code Online (Sandbox Code Playgroud)

请注意,该FormValue类型将使所有属性成为可选,因为可以禁用控件。如果您自己不禁用控件或将可禁用控件标记为可选,则该FormRawValue类型将为您提供更多帮助。对于您的情况,请使用后者。

interface UserFormControls {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  email: FormControl<string>;
  age: FormControl<number | null>;
}

export type UserForm = FormGroup<UserFormControls>;

export type User = FormRawValue<UserForm>;
// type User = {
//  firstName: string;
//  lastName: string;
//  email: string;
//  age: number | null;
// }
Run Code Online (Sandbox Code Playgroud)

堆栈闪电战


NOZ*_*OZU 2

您可以尝试对表单变量使用明确的赋值。下面是代码:

form!: FormGroup;
Run Code Online (Sandbox Code Playgroud)

您还可以使用声明来初始化表单控件,因为您正在表单上“发布”值但尚未更新。

form: FormGroup = new FormGroup({
      firstName: new FormControl<string>('', { nonNullable: true }),
      lastName: new FormControl('', { nonNullable: true }),
      email: new FormControl('', { nonNullable: true }),
      age: new FormControl<number | null>(null),
    });
Run Code Online (Sandbox Code Playgroud)

  • 你是对的,除了你说接口会消耗资源的部分。当转译为 JS 时,它们被完全删除,因此它们根本不占用空间。 (2认同)