首页 文章

Angular2嵌套模板驱动表单

提问于
浏览
36

这只是疯狂,看起来没有办法让一个表单中的一个输入在子组件中 .

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

问题是当子组件将具有任何类型的表单指令(ngModel,ngModelGroup或其他......)时,它将无法工作 .

This is only a problem in template driven forms

这是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; 
  }
}

5 回答

  • 20

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

    import { ControlContainer, NgForm } from '@angular/forms';
    
    @Component({
     ...,
     viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
    })
    export class ChildComponent {}
    

    Stackblitz Example

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

    Update

    如果您正在寻找 nested model driven form ,那么这里是类似的方法:

    @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

  • 2

    通过阅读一堆相关的github问题[1] [2],我还没有找到一种直接的方法来将一个子项 Component 的控件添加到父级 ngForm (有些人还称它们为嵌套表单,嵌套输入或复杂控件) .

    所以我将在这里展示的是一个适合我的解决方法,为父母和孩子使用单独的 ngForm 指令 . 这不是完美的,但它让我足够接近,我停在那里 .

    我用 ngForm 指令声明我的 childFormComponent (即它是 not 一个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

    另一种可行的解决方法

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

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

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

  • 5

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

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

    UPDATE: 这是Plunker有一些更改,我将子表单转换为模型驱动,因为在提交之前无法在表单驱动表单上进行更新以进行更新 .

  • 31

    我'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'是吸虫:plunker .

    它使用此指令:

    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);
        }
    }
    

    而这项服务:

    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] || [];
        }          
    }
    

    要在父组件中使用带有表单的指令:

    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();
       
    }
    

    然后在子组件中:

    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;
          
    }
    

相关问题