Angular2嵌套模板驱动表单

时间:2016-08-31 06:27:49

标签: angular angular2-forms

这只是一种疯狂,看起来没有办法让一个表格中的一个输入在儿童组件中。

我已阅读所有博客和教程以及所有内容,无法解决这个问题。

问题是当一个子组件要有任何形式的指令(ngModel,ngModelGroup或者其他......)时,它就不会工作。

这只是模板驱动表单中的一个问题

这是plunker

import { Component } from '@angular/core';

@Component({
  selector: 'child-form-component',
  template: ` 
  <fieldset ngModelGroup="address">
    <div>
      <label>Street:</label>
      <input type="text" name="street" ngModel>
    </div>
    <div>
      <label>Zip:</label>
      <input type="text" name="zip" ngModel>
    </div>
    <div>
      <label>City:</label>
      <input type="text" name="city" ngModel>
    </div>
  </fieldset>`
})

export class childFormComponent{


}

@Component({
  selector: 'form-component',
  directives:[childFormComponent],
  template: `
    <form #form="ngForm" (ngSubmit)="submit(form.value)">
      <fieldset ngModelGroup="name">
        <div>
          <label>Firstname:</label>
          <input type="text" name="firstname" ngModel>
        </div>
        <div>
          <label>Lastname:</label>
          <input type="text" name="lastname" ngModel>
        </div>
      </fieldset>

      <child-form-component></child-form-component>

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

    <pre>
      {{form.value | json}}
    </pre>

    <h4>Submitted</h4>
    <pre>    
      {{value | json }}
    </pre>
  `
})
export class FormComponent {

  value: any;

  submit(form) {
    this.value = form; 
  }
}

6 个答案:

答案 0 :(得分:42)

一个简单的解决方案是在您的子组件的ControlContainer数组中提供viewProviders,如:

import { ControlContainer, NgForm } from '@angular/forms';

@Component({
 ...,
 viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class ChildComponent {}

<强> Stackblitz Example

另请阅读本文,解释其工作原理。

<强>更新

如果您正在寻找嵌套模型驱动形式,那么这是类似的方法:

@Component({
  selector: 'my-form-child',
  template: `<input formControlName="age">`,
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class ChildComponent {
  constructor(private parent: FormGroupDirective) {}

  ngOnInit() {
    this.parent.form.addControl('age', new FormControl('', Validators.required))
  }
}

<强> Ng-run Example

更新2

如果您不确切知道哪种类型的ControlContainer包装了您的自定义组件(例如您的控件位于FormArray指令中),那么只需使用通用版本:

import { SkipSelf } from '@angular/core';
import { ControlContainer} from '@angular/forms';

@Component({
 ...,
 viewProviders: [{
   provide: ControlContainer,
   useFactory: (container: ControlContainer) => container,
   deps: [[new SkipSelf(), ControlContainer]],
 }]
})
export class ChildComponent {}

<强> Ng-run Example

答案 1 :(得分:22)

通过阅读一系列相关的github问题[1] [2],我还没有找到一种简单的方法来将角度添加到Component个孩子的控件中父ngForm(有些人也称它们为嵌套表格,嵌套输入或复杂控件)。

所以我要在这里展示的是为我工作的解决方法,为父母和孩子使用单独的ngForm指令。它并不完美,但它让我足够接近,我停在那里。

我使用childFormComponent指令声明我的ngForm(即是html表单标记,只有指令):

<fieldset ngForm="addressFieldsForm" #addressFieldsForm="ngForm">
  <div class="form-group">
    <label for="email">Email</label>
    <input type="email" class="form-control" [(ngModel)]="model.email" name="email" #email="ngModel" required placeholder="Email">
  </div>
  ...

然后,组件将addressFieldsForm作为属性公开,并将自身导出为template reference variable

@Component({
  selector: 'mst-address-fields',
  templateUrl: './address-fields.component.html',
  styleUrls: ['./address-fields.component.scss'],
  exportAs: 'mstAddressFields'
})
export class AddressFieldsComponent implements OnInit {
  @ViewChild('addressFieldsForm') public form: NgForm;
  ....

然后父表单可以像这样使用子表单组件:

  <form (ngSubmit)="saveAddress()" #ngFormAddress="ngForm" action="#">
    <fieldset>
      <mst-address-fields [model]="model" #addressFields="mstAddressFields"></mst-address-fields>
      <div class="form-group form-buttons">
        <button class="btn btn-primary" type="submit" [disabled]="!ngFormAddress.valid || !addressFields.form.valid">Save</button>
      </div>
    </fieldset>
  </form>

请注意,提交按钮会在ngFormAddressaddressFields表单上显式检查有效状态。这样我至少可以合理地组成复杂的形式,即使它有一些样板。

答案 2 :(得分:7)

另一种可能的解决方法:

@Directive({
    selector: '[provide-parent-form]',
    providers: [
        {
            provide: ControlContainer,
            useFactory: function (form: NgForm) {
                return form;
            },
            deps: [NgForm]
        }
    ]
})
export class ProvideParentForm {}

只需将此指令放在节点层次结构顶部的某个子组件中(在任何ngModel之前)。

工作原理:NgModel qualifies父表单与@Host()的依赖关系查找。因此,父组件中的表单对子组件中的NgModel不可见。但是我们可以使用上面演示的代码注入并在子组件中提供它。

答案 3 :(得分:2)

来自官方docsThis directive can only be used as a child of NgForm.

所以我认为你可以尝试将你的子组件包装在不同的ngForm中,并期望子组件的父组件结果@Output。如果您需要更多说明,请告诉我。

<强>更新 这里有Plunker进行了一些更改,我将子表单转换为模型驱动,因为在提交表单之前无法监听表单驱动表单以进行更新。

答案 4 :(得分:2)

I've created a solution using a directive and service. Once you add those to your module, the only other code change you need to make are at the form level in the templates. This works with dynamically added form fields and AOT. It also supports multiple unrelated forms on a page. Here's the plunker: plunker

它使用此指令:

&#13;
&#13;
import { Directive, Input } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NestedFormService } from './nested-form.service';

@Directive({
    selector: '[nestedForm]',
    exportAs: 'nestedForm'   
})
export class NestedFormDirective {    
    @Input('nestedForm') ngForm: NgForm;
    @Input() nestedGroup: string;
       
    public get valid() {
        return this.formService.isValid(this.nestedGroup);
    }

    public get dirty() {
        return this.formService.isDirty(this.nestedGroup);
    }

    public get touched() {
        return this.formService.isTouched(this.nestedGroup);
    }
    
    constructor(      
        private formService: NestedFormService
    ) { 
        
    }

    ngOnInit() {   
        this.formService.register(this.ngForm, this.nestedGroup);
    }

    ngOnDestroy() {
        this.formService.unregister(this.ngForm, this.nestedGroup);
    } 

    reset() {
        this.formService.reset(this.nestedGroup);
    }
}
&#13;
&#13;
&#13;

这项服务:

&#13;
&#13;
import { Injectable } from '@angular/core';
import { NgForm } from '@angular/forms';

@Injectable()
export class NestedFormService {

    _groups: { [key: string] : NgForm[] } = {};
      
    register(form: NgForm, group: string = null) {           
        if (form) {
            group = this._getGroupName(group);
            let forms = this._getGroup(group);        
            if (forms.indexOf(form) === -1) {
                forms.push(form);
                this._groups[group] = forms;
            }
        }
    }

    unregister(form: NgForm, group: string = null) {        
        if (form) {
            group = this._getGroupName(group);
            let forms = this._getGroup(group);
            let i = forms.indexOf(form);
            if (i > -1) {
                forms.splice(i, 1);
                this._groups[group] = forms;
            }
        }
    }

    isValid(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].invalid)
                return false;
        }
        return true;
    } 

    isDirty(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].dirty)
                return true;
        }
        return false;
    } 

    isTouched(group: string = null) : boolean {   
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            if (forms[i].touched)
                return true;
        }
        return false;
    } 

    reset(group: string = null) {
        group = this._getGroupName(group);         
        let forms = this._getGroup(group);
       
        for(let i = 0; i < forms.length; i++) {
            forms[i].onReset();
        }
    }

    _getGroupName(name: string) : string {
        return name || '_default';
    }

    _getGroup(name: string) : NgForm[] {        
        return this._groups[name] || [];
    }          
}
&#13;
&#13;
&#13;

在父组件中使用带有以下形式的指令:

&#13;
&#13;
import { Component, Input } from '@angular/core';
import { Person } from './person.model';

@Component({
    selector: 'parent-form',
    template: `  
        <div class="parent-box">

            <!--
            ngForm                        Declare Angular Form directive
            #theForm="ngForm"             Assign the Angular form to a variable that can be used in the template
            [nestedForm]="theForm"        Declare the NestedForm directive and pass in the Angular form variable as an argument
            #myForm="nestedForm"          Assign the NestedForm directive to a variable that can be used in the template
            [nestedGroup]="model.group"   Pass a group name to the NestedForm directive so you can have multiple forms on the same page (optional).
            -->

            <form 
                ngForm                  
                #theForm="ngForm" 
                [nestedForm]="theForm"
                #myForm="nestedForm" 
                [nestedGroup]="model.group">        

                <h3>Parent Component</h3> 
                <div class="pad-bottom">
                    <span *ngIf="myForm.valid" class="label label-success">Valid</span>
                    <span *ngIf="!myForm.valid" class="label label-danger">Not Valid</span>
                    <span *ngIf="myForm.dirty" class="label label-warning">Dirty</span>    
                    <span *ngIf="myForm.touched" class="label label-info">Touched</span>    
                </div> 

                <div class="form-group" [class.hasError]="firstName.invalid">
                    <label>First Name</label>
                    <input type="text" id="firstName" name="firstName" [(ngModel)]="model.firstName" #firstName="ngModel" class="form-control" required />
                </div>

                <child-form [model]="model"></child-form>
               
                <div>
                    <button type="button" class="btn btn-default" (click)="myForm.reset()">Reset</button>
                </div>
            </form>   
        </div>
    `
})
export class ParentForm {   
    
    model = new Person();
   
}
&#13;
&#13;
&#13;

然后在子组件中:

&#13;
&#13;
import { Component, Input } from '@angular/core';
import { Person } from './person.model';

@Component({
    selector: 'child-form',
    template: `  
        <div ngForm #theForm="ngForm" [nestedForm]="theForm" [nestedGroup]="model.group" class="child-box">
            <h3>Child Component</h3>
            <div class="form-group" [class.hasError]="lastName.invalid">
                <label>Last Name</label>
                <input type="text" id="lastName" name="lastName" [(ngModel)]="model.lastName" #lastName="ngModel" class="form-control" required />
            </div>
        </div>  
    `
})
export class ChildForm {    
    @Input() model: Person;
      
}
&#13;
&#13;
&#13;

答案 5 :(得分:0)

具有约100个动态形式的控件,控件的隐式包含可能使您成为模板驱动的主宰。以下内容将yurzui's miracle应用于所有地方。

export const containerFactory = (container: ControlContainer) => container;

@Directive({
  selector: '[ngModel]',
  providers: [{
      provide: ControlContainer,
      deps: [[new Optional(), new SkipSelf(), ControlContainer]],
      useFactory: containerFactory
  }]
})
export class ControlContainerDirective { }

Stackblitz Example