简体   繁体   English

angular 2 模型驱动的嵌套表单组件

[英]angular 2 model driven nested form components

What I have:我有什么:

I am building an ionic 2 app and have built a basic angular 2 component that contains我正在构建一个 ionic 2 应用程序并构建了一个基本的 angular 2 组件,其中包含

  • An input field一个输入字段

  • A label to display the inputs title显示输入标题的标签

  • A label to display any validation errors显示任何验证错误的标签

I will refer to this as my input component我将把它称为我的输入组件

I have a page component with a form on it, and currently have text inputs.我有一个页面组件,上面有一个表单,目前有文本输入。 1 regular input (password) and 1 input wrapped in my input component (username). 1 个常规输入(密码)和 1 个输入包含在我的输入组件(用户名)中。

this is the relevant portion of my page component这是我的页面组件的相关部分

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    username: ['', Validators.required],
    password: ['', Validators.required]
  });
}

This is the page component template这是页面组件模板

<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">

  <!-- My input component -->
  <aw-input-text id="username" name="Username" [formInput]="loginForm.controls.username"></aw-input-text>

  <!-- A standard input control -->
  <ion-item [class.error]="loginForm.controls.password.errors">
    <ion-label floating>Password</ion-label>
    <ion-input type="text" value="" name="password" formControlName="password"></ion-input>
    <p *ngIf="loginForm.controls.password.errors">This field is required!</p>
  </ion-item>

  <button type="submit" class="custom-button" [disabled]="!loginForm.valid" block>Login</button>

</form>

This is the template for my input component这是我的输入组件的模板

<!-- Component template -->
<form [formGroup]="formGroup">
    <ion-item>
        <ion-label floating>{{inputName}}</ion-label>
        <ion-input type="text" formControlName="inputValue"></ion-input>
        <p *ngIf="!formGroup.controls.inputValue.valid">This field is required!</p>
    </ion-item>
</form>

and this is the input component这是输入组件

import {Component, Input} from '@angular/core';
import {FormBuilder} from '@angular/forms';

@Component({
  selector: 'aw-input-text',
  templateUrl: 'build/shared/aw-input-text/aw-input-text.html'
})
export class AwInputText {

  @Input('formInput')
  public formInput;
  @Input('name')
  public inputName;
  public formGroup;
  constructor(private formBuilder: FormBuilder) {
  }

  ngOnInit() {
     this.formGroup = this.formBuilder.group({
        inputValue: this.formInput
     });
  }

}

The component renders correctly.组件正确呈现。

The problem:问题:

The input inside the component doesn't update the valid state of the the form it is in.组件内的输入不会更新它所在表单的有效状态。

When I fill out the username then the password the form becomes valid当我填写用户名时,密码表格就生效了

When I fill out the password then the username the form remains invalid当我填写密码时,表单中的用户名仍然无效

So the form can see the valid state of the input component, it's just that the input component changing valid state doesn't trigger the form to update.所以表单可以看到输入组件的有效状态,只是输入组件改变有效状态不会触发表单更新。

Possible solution 1可能的解决方案 1

As described in this article and plunk如本文所述和 plunk

https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2 https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2

https://plnkr.co/edit/clTbNP7MHBbBbrUp20vr?p=preview https://plnkr.co/edit/clTbNP7MHBbBbrUp20vr?p=preview

I could modify my page component to create a form group that contains a nested form group for every form control that I want to use inside of my input component我可以修改我的页面组件来创建一个表单组,其中包含一个嵌套的表单组,用于我想在输入组件中使用的每个表单控件

ngOnInit() {
  this.loginForm = this.formBuilder.group({
    username: this.formBuilder.group({
      username: ['', Validators.required],
    }),
    password: ['', Validators.required],
  });
}

This solution fits the scenario in the article where they are adding an array of input controls, but in my case I think this feels hacky此解决方案适合文章中添加输入控件数组的场景,但就我而言,我认为这感觉很糟糕

Possible solution 2可能的解决方案 2

Another hacky solution I have considered is using an @output directive from my input component to trigger an event on the page component that refreshes the form whenever the input component is updated.我考虑过的另一个 hacky 解决方案是使用来自我的输入组件的 @output 指令来触发页面组件上的事件,每当输入组件更新时,该事件都会刷新表单。

Update to the input component更新输入组件

this.formGroup.controls.inputValue.valueChanges.subscribe(value => {
  this.formUpdated.emit({
    value: value
  })
});

Update to the page component更新页面组件

public onUpdated(value){
  this.loginForm.updateValueAndValidity();
}

and an update the page component template并更新页面组件模板

<aw-input-text id="username" name="Username" (updated)="onUpdated($event)" [formInput]="loginForm.controls.username"></aw-input-text>

This does give me the desired functionality, but I think it also seems a bit hacky having an event handler on every form page to make the input component work.这确实为我提供了所需的功能,但我认为在每个表单页面上都有一个事件处理程序以使输入组件工作似乎也有点麻烦。

The question问题

Is there a way for me to make my component update the valid state of the form it is in (keeping in mind that I would want to re-use this component multiple times in each form) without resorting to the solution described above.有没有办法让我的组件更新它所在表单的有效状态(请记住,我希望在每个表单中多次重复使用该组件),而无需求助于上述解决方案。

Thanks for the answers guys.谢谢各位的回答。 In the end we created two components, a custom-form component and a custom-input component.最后我们创建了两个组件,一个自定义表单组件和一个自定义输入组件。

We nest as many custom-input components as we need inside the custom-form component and the custom-form component uses @ContentChildren to identify and register all the child custom-input components.我们将尽可能多的自定义输入组件嵌套在自定义表单组件中,并且自定义表单组件使用@ContentChildren 来识别和注册所有子自定义输入组件。

This way we don't have to pass the form into every input, and we don't have a mess of nested form groups for every input.这样我们就不必将表单传递到每个输入中,并且我们不会为每个输入都有一堆嵌套的表单组。

// Each CustomInputText component exposes a FormControl and 
// a control definition which has additional info about the control
@ContentChildren(CustomInputText, {descendants: true})
public customInputComponents: QueryList<CustomInputText>;

private initialised;

public ngAfterContentChecked() {
  // Only initialise the form group once
  if (!this.initialised) {
    this.initialised = true;
    this.customInputComponents.forEach((input)=>{
        this.formGroup.addControl(input.controlDefinition.id, input.formControl); 
    });
  }
}

You can @Input your loginForm into nested component, as let's say parentForm .您可以@Input您的loginForm到嵌套组件中,比如parentForm Then register nested formGroup to parentForm on child component init, and unregister it on child component destroy.然后在子组件初始化时将嵌套的formGroup注册到parentForm ,并在子组件销毁时取消注册。

What I did in my case (nested dynamic form as well) is somehow similar to Marcin's response.我在我的案例中所做的(嵌套动态形式也是如此)在某种程度上类似于 Marcin 的响应。

I'm passing existing FormGroup as parentForm and my Component 's view looks like this:我将现有的FormGroup作为parentForm传递,我的Component的视图如下所示:

<fieldset [formGroup]="parentForm">
    <label *ngIf="label" [attr.for]="key">{{label}}</label>
    <input [id]="key" [type]="type" [formControlName]="key" />
</fieldset>

It suits my needs.它适合我的需求。 Hope it can help you as well.希望它也能帮到你。

UPDATE: I've created a library for speeding up dynamic forms creation.更新:我创建了一个用于加速动态表单创建的库。 You can take a look to figure out how I use the technique from this answer: https://www.npmjs.com/package/dorf您可以看看我如何使用这个答案中的技术: https : //www.npmjs.com/package/dorf

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM