【问题标题】:Handling big reactive forms in Angular 7在 Angular 7 中处理大型响应式表单
【发布时间】:2019-12-22 07:33:42
【问题描述】:

我正在尝试寻找一种更好的方法来处理复杂的角度形式。 表格真的很大,我需要找到一种方法来降低复杂性。

这里是表单结构的一个例子:

{
    "fieldA" : ...,
    "fieldB" : ...,
    "fieldC" : ...,
    "profile": {
        "username": ...,
        "email": ...,
        "firstName": ...,
        "lastName": ...,
        ...
    },
    "settings": {
        "enableEmailNotification": ...,
        "notificationsEmail": ..., // required when enableEmailNotification
        ...
    },
    ...
}

在某些情况下验证器会即时更改,例如当enableEmailNotification=true 时,组件会将Required 验证器添加到notificationsEmail

这里是研究过的选项:

选项 #0 - 经典

sample on github

这种方法使用一种形式和一种组件。

优点:

  • 代码很多,但很简单

缺点:

  • 所有逻辑都在一个地方。对于我来说,这个组件变得太大并且难以阅读或维护
  • UI 也变得足够大了

选项 #1 - 将 FormGroup 传递给子组件

sample on github

这种方法将formGroup 作为@Input() 属性发送到内部组件。

优点:

  • 缩小部分视图

缺点:

  • 表单创建和验证规则仍在父组件中
  • 仅缩小视图大小
  • 在根组件中创建了验证逻辑,但在子组件中显示错误

选项 #2 - 创建自定义 ControlValueAccessor

sample on github

基于this article,我们可以创建自定义的ControlValueAccessor,它将返回一个表单的一部分的对象。

优点:

  • 以多种形式拆分表单。表单可以拆分为更小的独立部分。

缺点:

  • 为表单值保留 JS 对象。看起来不太好

【问题讨论】:

    标签: javascript angular typescript angular-material


    【解决方案1】:

    我对大型复杂表单的策略是拥有一个包装组件和子组件。每个子组件都有自己的表单服务,包装器有一个主表单服务,其中注入了子表单服务,考虑一下

    @Component({
      selector: 'form-wrapper',
      template: `
        <form [formGroup]="form" (submit)="save()">
          <sub-form-a></sub-form-a>
          <sub-form-b></sub-form-b>
          <input type="submit" value="Submit Form">
        </form>
      `,
      providers: [MasterFormService, FormAService, FormBService]
    })
    export class FormWrapper {
      constructor(private formService: MasterFormService) { }
      save() {
        // whatever save actions here
      }
    }
    
    @Component({ // form b compoent is basically the same
      selector: 'sub-form-a',
      template: `
        ... whatever template belongs to form a ..
      `
    })
    export class FormAComponent {
      form: FormGroup
      constructor(private formService: FormAService) {
        this.form = this.formService.form;
        // form a specific actions
      }
    }
    
    @Injectable()
    export class MasterFormService {
      form: FormGroup;
      constructor(private fb: FormBuilder, formAService: FormAService, formBService: FormBService) {
        this.form = this.fb.group({
          groupA: this.formAService.form,
          groupB: this.formBService.form,
        });
      }
    }
    
    @Injectable() // formB service is basically the same
    export class FormAService {
      form: FormGroup;
      constructor(private fb: FormBuilder) {
        this.form = this.fb.group({
          .. whatever fields belong to form a ..
        });
      }
    }
    

    此方法创建高度可重用的子表单,并让您模块化/隔离表单逻辑和模板。我经常发现子表单通常属于多个地方,因此它使我的代码非常干燥。特别是在您的示例中,您可以轻松地在应用程序的其他地方重用设置表单和配置文件表单组件。一两次我什至再次嵌套了这个结构以获得极其复杂的形式。

    缺点是结构可能看起来很复杂,但你很快就会习惯。

    【讨论】:

    • 谢谢。看起来很有趣。我会试一试的。
    • 你将如何通知 sub-form-b​​ 关于 sub-form-a 的变化,例如根据 sub-form-a 中的值添加或删除所需的验证器
    【解决方案2】:

    就个人而言,对于大型复杂表单,我希望将表单逻辑保留在一个组件中,这样很容易链接可观察对象,但使用几个服务作为助手。用于初始化表单和处理操作(返回新表单值、验证、授权等)

    • 逻辑/跟踪在FormComponent
    • 表单的初始化在FormInitService
    • 动作在一个pr多个中处理FormActionService(s)

    表单组件

    export class FormComponent implements OnInit {
      constructor(
        private formActionService: FormActionService,
        private formInitService: FormInitService
      ) { }
    
      ngOnInit() {
        this.form = this.FormInitService.getForm();
        this._trackFieldA();
        this._trackProfile();
      }
    
      // Track a single field
      private _trackFieldA() {
        this.form.controls.fieldA.valueChanges.pipe(
          distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
        ).subscribe(fieldA => {
          console.log('Field A Changed');
          this.formActionService.doSomething();
        });
      }
    
      // Track a group
      // Use ['controls'] for nested controls to skip typechecking errors
      private _trackProfile() {
        combineLatest(
          this.form.controls.profile['controls'].username.valueChanges,
          this.form.controls.profile['controls'].email.valueChanges,
        ).pipe(
          distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
        ).subscribe(profile => {
          console.log('Some profile field changed');
          this.formActionService.doSomething();
        });
      }
    }
    

    FormInitService

    export class FormInitService {
      constructor(
        private formBuilder: FormBuilder
      ) { }
    
      public getForm(): FormGroup {
        return this.formBuilder.group({
          fieldA: 'Some init value',
          fieldB: 'Some init value',
          profile: this.formBuilder.group({
            username: 'Some init value',
            email: 'Some init value',
            ...
          }),
          ...
        });
      }
    }
    

    FormActionService

    export class FormActionService {
      public doSomething(): any | void {
        console.log('Something')
      }
    }
    

    您在 FormComponent 和模板中仍然有相当多的代码,但它真的很容易阅读和维护。拆分成多个组件通常会变得非常混乱,尤其是在团队工作时,或者需要进行一些(巨大的)重构时。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-03
      • 1970-01-01
      • 2014-03-18
      • 2013-02-20
      • 1970-01-01
      • 2020-05-02
      相关资源
      最近更新 更多