Подтвердить что ты не робот

Angular2 вложенная форма с шаблоном

Это просто безумие, похоже, что нет способа иметь форму, которую один из входных элементов находится в дочернем компоненте.

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

Проблема в том, что когда дочерний компонент будет иметь какие-либо директивы формы (ngModel, ngModelGroup или что-то еще..), он не будет работать.

Это только проблема в шаблонах управляемых форм

Это плункер:

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

Ответ 1

Одно простое решение - предоставить ControlContainer в массиве viewProviders вашего дочернего компонента, например:

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

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

Пример Stackblitz

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

Обновление

Если вы ищете вложенную модель, управляемую формой, то вот похожий подход:

@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 Пример

Обновление 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 Пример

Ответ 2

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

@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. Таким образом, я могу, по крайней мере, разумно составлять сложные формы, хотя у него есть некоторая схема.

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

С ~ 100 элементами управления в динамических формах неявное включение элементов управления может сделать вас управляемым шаблоном. Следующее применимо чудо юрзуй везде.

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

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

Пример Stackblitz