30

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

同样我们可以通过实现ControlValueAccessor和使用自定义组件<my-cmp formControlName="foo"></my-cmp>来做到这一点,我们如何才能为组实现这种效果

<my-cmp formGroupName="aGroup"></my-cmp>

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


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

Parent 具有以下使用 构建的表单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">
4

2 回答 2

67

rusev 的回答中提到了我缺少的那部分,那就是注入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) {
  }
}

Notice how we need a wrapper for the inputs. We don't want a form because this will already be inside a form. So we use an ng-container. This will be striped away from the final DOM so there's no unnecessary element.

Now we can just use this component.

@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) {}
}

You can see a live demo on StackBlitz.


This is an improvement over rusev's answer in a few aspects:

  • no custom groupName input; instead we use the formGroupName provided by Angular
  • no need for @SkipSelf decorator, since we're not injecting the parent control, but the one we need
  • no awkward group.control.get(groupName) which is going to the parent in order to grab itself.
于 2017-09-03T15:51:14.383 回答
5

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: '',
      })
    });
  }
}
于 2017-08-17T13:18:13.617 回答