2016-08-31 5 views
24

Это просто безумие, похоже, что нет способа иметь форму, в которой один из ее вкладов находится в дочернем компоненте.Angular2 inested template driven form

Я прочитал все блоги и учебные пособия и все, без возможности этого.

Проблема заключается в том, что у дочернего компонента будут какие-либо директивы формы (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; 
    } 
} 
+0

См. Также https://medium.com/@a.yurich.zuev/angular-nested-template-driven-form-4a3de2042475 – yurzui

ответ

2

От официального docs: This directive can only be used as a child of NgForm.

Так что я думаю, что вы можете попробовать оберните свой дочерний компонент в разные ngForm и ожидайте в результат родительского компонента @Output дочернего компонента. Дайте мне знать, если вам нужно больше разъяснений.

UPDATE: Вот Plunker с некоторыми изменениями, я преобразовал ребенок форма для управляемых моделей, потому что нет никакого способа, чтобы слушать по форме приводится форма для обновлено до того, как будет занесены.

+0

Компоненты изолированы в ng2, поэтому на первое предложение должно отвечать. Прикрепление плунжера – Mikki

+3

Вы используете реактивный способ определения элементов управления, где в качестве всего вопроса о формах, управляемых шаблонами. –

+0

Я ответил на ваш вопрос, почему он не работает, потому что ваш дочерний компонент не имеет ngForm. – Mikki

15

Чтения через кучу связанных GitHub вопросы [1][2], я не нашел простой способ сделать угловой добавить ребенок Component «ы управляет родителем 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> 

Обратите внимание, что кнопка отправки явно проверяет действительное состояние на обоих ngFormAddress и addressFields формы. Таким образом, я могу, по крайней мере, разумно составлять сложные формы, хотя у него есть некоторая схема.

+1

Йоханнес Рудольф для президента. Ты спас мой день :) Спасибо! – speti43

+5

Основываясь на вашем ответе, я нашел способ, чтобы дочерние компоненты могли автоматически подключаться к общей форме: https://gist.github.com/jehugaleahsa/c40fb64d8613cfad8f1e1faa4c2a7e33 –

+0

@TravisParks вы должны добавить свое полное решение в качестве нового ответа на этом посту его отлично работает! СПАСИБО!! – squadwuschel

2

Другой возможный обходной путь:

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

Просто поместите эту директиву в компоненте ребенка где-то в верхней части иерархии узлов (до любого ngModel).

Как это работает: NgModel qualifies Поиск зависимостей родительской формы с помощью @Host(). Таким образом, форма из родительского компонента не отображается в NgModel в дочернем компоненте. Но мы можем вводить и предоставлять его внутри дочернего компонента, используя приведенный выше код.

+0

Отличное решение! Вы должны написать сообщение в блоге об этом. –

+0

Мне нравится запись в блоге, чтобы глубже углубиться в это – CaffGeek

+0

@artem, это не работает в моем примере plunker: https://plnkr.co/edit/VSUzQtrtEmXSIEPzAjXb Знаете ли вы, почему? –

1

Я создал решение с использованием директивы и сервиса. После того, как вы добавите их в свой модуль, только одно изменение кода, которое вам нужно сделать, находится на уровне формы в шаблонах. Это работает с динамически добавленными полями формы и AOT. Он также поддерживает несколько несвязанных форм на странице. Вот плункер: 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] || []; 
 
    }   
 
}

Чтобы использовать директиву в родительском компе onent с формой:

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

12

Одно простое решение заключается в обеспечении ControlContainer в viewProviders массиве вашего ребенка компонента например:

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

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

Stackblitz Example

Читайте также в этой статье, что объясняет, почему он работает.

Update

Если вы ищете вложенной модели ведомой формы то здесь подобный подход:

@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

+0

Святое дерьмо. Я ждал FOREVER для этой функции! Спасибо за сообщение. –

+0

Это не работает для меня - я получаю «Нет провайдера для NgForm!». Я подозреваю, что это связано с тем, что у нашего пользовательского элемента управления формы есть , где вставляются различные элементы управления. Есть идеи, что такое работа? –

+0

@JakeShakesworth Пожалуйста, создайте пример плункера или stackblitz для воспроизведения – yurzui