如何使用大量自定义组件创建复杂的表单? [英] How to tackle creating complex form with lots of custom components?

查看:28
本文介绍了如何使用大量自定义组件创建复杂的表单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我从 angular2 应用程序生成的 html 如下所示:

<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)"><面板组件><中间组件><带有输入的内部组件><输入/><带有输入的内部组件><中间组件></面板组件><面板组件><中间组件><带有输入的内部组件><输入/><带有输入的内部组件><中间组件></面板组件><!--许多许多领域--><button type="submit">提交</button></表单></应用程序>

如何设置外部

以便在提交时验证所有内部输入?我是否必须通过 @Input()panel-component 一直向下传递 myForminner-component-with-输入?或者有其他方法吗?

在我的应用程序中,我有一个非常大的表单,包含多个面板、子面板、选项卡、模态等,我需要能够在提交时一次性验证它们.

互联网上的所有教程和资源都只讨论跨越一个组件/模板的表单.

解决方案

当涉及父/子关系时,您将在整个 Angular 源代码中看到的一个常见模式是父类型将自身添加为自身的提供者.这样做是允许子组件注入父组件.由于 分层 DI.下面是一个可能看起来像的例子

导出抽象类 FormControlContainer {抽象 addControl(name: string, control: FormControl): void;抽象删除控制(名称:字符串):无效;}导出 const formGroupContainerProvider: any = {提供:FormControlContainer,useExisting: forwardRef(() => NestedFormComponentsComponent)};@成分({选择器:'嵌套形式组件',模板:`...`,指令:[REACTIVE_FORM_DIRECTIVES,ChildComponent],提供者:[formGroupContainerProvider]})导出类 ParentComponent 实现 FormControlContainer {表单:FormGroup = new FormGroup({});addControl(名称:字符串,控件:FormControl){this.form.addControl(name, control);}删除控制(名称:字符串){this.form.removeControl(name);}}

一些注意事项:

  • 我们使用接口/抽象父级 (FormControlContainer) 有几个原因

    1. 它将ParentComponentChildComponent 分离.孩子不需要知道任何关于特定 ParentComponent 的信息.它只知道 FormControlContainer 和它所拥有的契约.
    2. 我们只通过接口契约公开 ParentComponent 上需要的方法.

  • 我们只广告 ParentComponentFormControlContainer,所以后者是我们要注入的内容.

  • 我们以 formControlContainerProvider 的形式创建一个提供者,然后将该提供者添加到 ParentComponent.由于分层 DI,现在所有子级都可以访问父级.

  • 如果您不熟悉 forwardRef这是一篇很棒的文章

现在在孩子(人)中你可以做

@Component({选择器:'子组件',模板:`...`,指令:[REACTIVE_FORM_DIRECTIVES]})导出类 ChildComponent 实现 OnDestroy {名字:表单控件;姓氏:表单控件;构造函数(私有_parent:FormControlContainer){this.firstName = new FormControl('', Validators.required);this.lastName = new FormControl('', Validators.required);this._parent.addControl('firstName', this.firstName);this._parent.addControl('lastName', this.lastName);}ngOnDestroy() {this._parent.removeControl('firstName');this._parent.removeControl('lastName');}}

IMO,这是一个比通过 @Input 传递 FormGroup 更好的设计.如前所述,这是整个 Angular 源代码中的通用设计,所以我认为可以肯定地说这是一种可接受的模式.

如果你想让子组件更可重用,你可以让构造函数参数@Optional().

以下是我用来测试上述示例的完整源

import {组件、OnInit、ViewChildren、QueryList、OnDestroy、forwardRef、Injector来自'@angular/core';进口 {表单控件,表单组,控制容器,验证器,表单组指令,REACTIVE_FORM_DIRECIVES来自'@angular/forms';导出抽象类 FormControlContainer {抽象 addControl(name: string, control: FormControl): void;抽象删除控制(名称:字符串):无效;}导出 const formGroupContainerProvider: any = {提供:FormControlContainer,useExisting: forwardRef(() => NestedFormComponentsComponent)};@成分({选择器:'嵌套形式组件',模板:`<form [formGroup]="form"><子组件></子组件><div><button type="button" (click)="onSubmit()">提交</button>

</表单>`,指令:[REACTIVE_FORM_DIRECTIVES, forwardRef(() => ChildComponent)],提供者:[formGroupContainerProvider]})导出类 NestedFormComponentsComponent 实现 FormControlContainer {form = new FormGroup({});onSubmit(e) {如果(!this.form.valid){console.log('表单无效!')if (this.form.hasError('required', ['firstName'])) {console.log('名字是必填项.');}if (this.form.hasError('required', ['lastName'])) {console.log('需要姓氏.');}} 别的 {console.log('表单有效!');}}addControl(name: string, control: FormControl): void {this.form.addControl(name, control);}removeControl(名称:字符串):无效{this.form.removeControl(name);}}@成分({选择器:'子组件',模板:`<div><label for="firstName">名字:</label><input id="firstName" [formControl]="firstName" type="text"/>

<div><label for="lastName">姓氏:</label><input id="lastName" [formControl]="lastName" type="text"/>

`,指令:[REACTIVE_FORM_DIRECTIVES]})导出类 ChildComponent 实现 OnDestroy {名字:表单控件;姓氏:表单控件;构造函数(私有_parent:FormControlContainer){this.firstName = new FormControl('', Validators.required);this.lastName = new FormControl('', Validators.required);this._parent.addControl('firstName', this.firstName);this._parent.addControl('lastName', this.lastName);}ngOnDestroy() {this._parent.removeControl('firstName');this._parent.removeControl('lastName');}}

Let's say that my generated html from angular2 app looks like this:

<app>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)">
<panel-component>
    <mid-component>
        <inner-component-with-inputs>
            <input/>
        <inner-component-with-inputs>
    <mid-component>
</panel-component>
<panel-component>
    <mid-component>
        <inner-component-with-inputs>
            <input/>
        <inner-component-with-inputs>
    <mid-component>
</panel-component>

<!-- many many many fields -->

<button type="submit">Submit</button>
</form>
</app>

How can I set up my outer <form> in such a way that I can validate all inner inputs on submit? Do I have to pass myForm through @Input() all the way down from panel-component to inner-component-with-inputs? Or is there other way?

In my application I have very big form with multiple panels, subpanels, tabs, modals etc. and I need to be able to validate it all at once on submit.

All the tutorials and resources on the internet talk only about forms spanning one component/template.

解决方案

A common pattern you will see throughout the Angular source code, when parent/child relationships are involved, is the parent type adding itself as a provider to itself. What this does is allow child component to inject the parent. And there will on only be one instance of the parent component all the way down the component tree because of hierarchical DI. Below is an example of what that might look like

export abstract class FormControlContainer {
  abstract addControl(name: string, control: FormControl): void;
  abstract removeControl(name: string): void;
}

export const formGroupContainerProvider: any = {
  provide: FormControlContainer,
  useExisting: forwardRef(() => NestedFormComponentsComponent)
};

@Component({
  selector: 'nested-form-components',
  template: `
    ...
  `,
  directives: [REACTIVE_FORM_DIRECTIVES, ChildComponent],
  providers: [formGroupContainerProvider]
})
export class ParentComponent implements FormControlContainer {
  form: FormGroup = new FormGroup({});

  addControl(name: string, control: FormControl) {
    this.form.addControl(name, control);
  }

  removeControl(name: string) {
    this.form.removeControl(name);
  }
}

Some notes:

  • We're using an interface/abstract parent (FormControlContainer) for a couple reasons

    1. It decouples the ParentComponent from the ChildComponent. The child doesn't need to know anything about the specific ParentComponent. All it knows about is the FormControlContainer and the contract that is has.
    2. We only expose methods on the ParentComponent that want, through the interface contract.

  • We only advertise ParentComponent as FormControlContainer, so the latter is what we will inject.

  • We create a provider in the form of the formControlContainerProvider and then add that provider to the ParentComponent. Because of hierarchical DI, now all the children have access to the parent.

  • If you are unfamiliar with forwardRef, this is a great article

Now in the child(ren) you can just do

@Component({
  selector: 'child-component',
  template: `
    ...
  `,
  directives: [REACTIVE_FORM_DIRECTIVES]
})
export class ChildComponent implements OnDestroy {
  firstName: FormControl;
  lastName: FormControl;

  constructor(private _parent: FormControlContainer) {
    this.firstName = new FormControl('', Validators.required);
    this.lastName = new FormControl('', Validators.required);
    this._parent.addControl('firstName', this.firstName);
    this._parent.addControl('lastName', this.lastName);
  }

  ngOnDestroy() {
    this._parent.removeControl('firstName');
    this._parent.removeControl('lastName');
  }
}

IMO, this is a much better design than passing the FormGroup through @Inputs. As stated earlier, this is a common design throughout the Angular source, so I think it's safe to say that it's an acceptable pattern.

If you wanted to make the child components more reusable, you could make the constructor parameter @Optional().

Below is the complete source I used to test the above examples

import {
  Component, OnInit, ViewChildren, QueryList, OnDestroy, forwardRef, Injector
} from '@angular/core';
import {
  FormControl,
  FormGroup,
  ControlContainer,
  Validators,
  FormGroupDirective,
  REACTIVE_FORM_DIRECTIVES
} from '@angular/forms';


export abstract class FormControlContainer {
  abstract addControl(name: string, control: FormControl): void;
  abstract removeControl(name: string): void;
}

export const formGroupContainerProvider: any = {
  provide: FormControlContainer,
  useExisting: forwardRef(() => NestedFormComponentsComponent)
};

@Component({
  selector: 'nested-form-components',
  template: `
    <form [formGroup]="form">
      <child-component></child-component>
      <div>
        <button type="button" (click)="onSubmit()">Submit</button>
      </div>
    </form>
  `,
  directives: [REACTIVE_FORM_DIRECTIVES, forwardRef(() => ChildComponent)],
  providers: [formGroupContainerProvider]
})
export class NestedFormComponentsComponent implements FormControlContainer {

  form = new FormGroup({});

  onSubmit(e) {
    if (!this.form.valid) {
      console.log('form is INVALID!')
      if (this.form.hasError('required', ['firstName'])) {
        console.log('First name is required.');
      }
      if (this.form.hasError('required', ['lastName'])) {
        console.log('Last name is required.');
      }
    } else {
      console.log('form is VALID!');
    }
  }

  addControl(name: string, control: FormControl): void {
    this.form.addControl(name, control);
  }

  removeControl(name: string): void {
    this.form.removeControl(name);
  }
}

@Component({
  selector: 'child-component',
  template: `
    <div>
      <label for="firstName">First name:</label>
      <input id="firstName" [formControl]="firstName" type="text"/>
    </div>
    <div>
      <label for="lastName">Last name:</label>
      <input id="lastName" [formControl]="lastName" type="text"/>
    </div>
  `,
  directives: [REACTIVE_FORM_DIRECTIVES]
})
export class ChildComponent implements OnDestroy {
  firstName: FormControl;
  lastName: FormControl;

  constructor(private _parent: FormControlContainer) {
    this.firstName = new FormControl('', Validators.required);
    this.lastName = new FormControl('', Validators.required);
    this._parent.addControl('firstName', this.firstName);
    this._parent.addControl('lastName', this.lastName);
  }


  ngOnDestroy() {
    this._parent.removeControl('firstName');
    this._parent.removeControl('lastName');
  }
}

这篇关于如何使用大量自定义组件创建复杂的表单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆