创建可重用的 FormGroup

Create a reusable FormGroup

我知道创建自定义 控件 作为组件,但我不知道如何创建自定义

同样我们可以通过implementing ControlValueAccessor and using a custom component like <my-cmp formControlName="foo"></my-cmp>, how can we achieve this effect for a group?

来做到这一点
<my-cmp formGroupName="aGroup"></my-cmp>

两个非常常见的用例是 (a) 将长表单分成多个步骤,每个步骤在一个单独的组件中,以及 (b) 封装一组出现在多个表单中的字段,例如地址(一组国家、州、城市、地址、门牌号)或出生日期(年、月、日)。


示例用法(不是实际工作代码)

父级使用 FormBuilder 构建了以下表单:

// parent model
form = this.fb.group({
  username: '',
  fullName: '',
  password: '',
  address: this.fb.group({
    country: '',
    state: '',
    city: '',
    street: '',
    building: '',
  })
})

父模板(为简洁起见不可访问且非语义):

<!-- parent template -->
<form [groupName]="form">
  <input formControlName="username">
  <input formControlName="fullName">
  <input formControlName="password">
  <address-form-group formGroup="address"></address-form-group>
</form>

现在 AddressFormGroupComponent 知道如何处理内部具有这些特定控件的组。

<!-- child template -->
<input formControlName="country">
<input formControlName="state">
<input formControlName="city">
<input formControlName="street">
<input formControlName="building">

Angular 表单没有组名称和表单控件名称的概念。但是,您可以通过将子模板包装在表单组中来轻松解决此问题。

这是一个类似于您发布的标记的示例 - https://plnkr.co/edit/2AZ3Cq9oWYzXeubij91I?p=preview

 @Component({
  selector: 'address-form-group',
  template: `
    <!-- child template -->
    <ng-container [formGroup]="group.control.get(groupName)">
      <input formControlName="country">
      <input formControlName="state">
      <input formControlName="city">
      <input formControlName="street">
      <input formControlName="building">
    </ng-container>
  `
})
export class AddressFormGroupComponent  { 
  @Input() public groupName: string;

  constructor(@SkipSelf() public group: ControlContainer) { }
}

@Component({
  selector: 'my-app',
  template: `
    <!-- parent template -->
    <div [formGroup]="form">
      <input formControlName="username">
      <input formControlName="fullName">
      <input formControlName="password">
      <address-form-group groupName="address"></address-form-group>
    </div>
    {{form?.value | json}}
  `
})
export class AppComponent { 
  public form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      username: '',
      fullName: '',
      password: '',
      address: this.fb.group({
        country: '',
        state: '',
        city: '',
        street: '',
        building: '',
      })
    });
  }
}

, and that is injecting the ControlContainer.

中提到了我遗漏的部分

事实证明,如果您将 formGroupName 放在组件上,并且如果该组件注入 ControlContainer,您将获得对包含该表单的容器的引用。从这里开始很容易。

我们创建一个子表单组件。

@Component({
  selector: 'sub-form',
  template: `
    <ng-container [formGroup]="controlContainer.control">
      <input type=text formControlName=foo>
      <input type=text formControlName=bar>
    </ng-container>
  `,
})
export class SubFormComponent {
  constructor(public controlContainer: ControlContainer) {
  }
}

请注意我们如何需要输入包装器。我们不需要表单,因为它已经在表单中了。所以我们使用 ng-container。这将从最后的 DOM 中删除,因此没有不必要的元素。

现在我们可以直接使用这个组件了。

@Component({
  selector: 'my-app',
  template: `
    <form [formGroup]=form>
      <sub-form formGroupName=group></sub-form>
      <input type=text formControlName=baz>
    </form>
  `,
})
export class AppComponent  {
  form = this.fb.group({
    group: this.fb.group({
      foo: 'foo',
      bar: 'bar',
    }),
    baz: 'baz',
  })

  constructor(private fb: FormBuilder) {}
}

你可以看到一个live demo on StackBlitz.


这比 rusev 的答案在几个方面有所改进:

  • 没有自定义 groupName 输入;相反,我们使用 Angular
  • 提供的 formGroupName
  • 不需要 @SkipSelf 装饰器,因为我们 不是 注入父控件,而是我们需要的
  • 不尴尬group.control.get(groupName)这是去父级为了抢自己