Угловые реактивные формы в повторно используемых дочерних компонентах: проблема с FormArrays: `formGroupName` должен использоваться с родительской директивой formGroup - PullRequest
1 голос
/ 02 июля 2019

Я пытаюсь описать проблему на примере ( полный пример на stackblitz )

Если я пытаюсь поместить некоторые части реактивной формы в виде простых "formControls""или" formGroups "с дочерними компонентами, проблем нет.(См. Пример на стекаблице выше).FormGroupDirective работает как положено.

Но если я попытаюсь поместить FormArray в дочерний компонент, у меня возникнут проблемы из-за:

<div [formGroupName]="i">
  <!--
    Error: formGroupName must be used 
    with a parent formGroup directive.
  -->
<div>

Реактивная форма - это простая форма:

  ngOnInit () {
    this.newForm = this.fb.group({
      id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
      chapter: this.fb.group({
        name: ['Some Chapter', Validators.required]
      }),
      faq: this.fb.array(this.faqArray)
    });
  }

Как уже упоминалось выше, с id и chapter проблем нет, они реализованы в пользовательских дочерних компонентах, таких как:

<fe-input 
  [controlPath]="['id']"
  placeholder="Child-FormControl ID"
></fe-input>

<my-form-group 
[controlPath]="['chapter', 'name']"
placeholder="Child-FormGroup Chapter"
[required]="true"
>
</my-form-group>

В приложении на stackblitz вы увидитерабочие части "ID" и "Глава".

Тот же самый подход с formArray:

<my-form-array 
[controlPath]="['faq']" 
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>

должен работать, как и ожидалось, но часть <div [formGroupName]="i"> вызывает только в пределахдочерний компонент вышеупомянутая ошибка выше:

Error: formGroupName must be used 
with a parent formGroup directive.

Если я использую это в исходном файле (где определяется реактивная форма), он работает без проблем.

Яперепутал, может я просто упустил из виду простую проблему?Кто-нибудь может мне помочь?Весь пример доступен здесь: ( >> stackblitz )

UPDATE 1:

Я интегрировал решение из @yurzui с


viewProviders: [{ 
  provide: ControlContainer, 
  useExisting: FormGroupDirective 
}]

и ошибка "Error: formGroupName must be used.." исчезла.После этого я интегрировал пользовательский компонент для полей formArray, и они не могут получить значение элемента управления.Я думаю, что я близок к решению.Это пользовательский компонент:

import { Component, OnInit, Input } from '@angular/core';
import {
  FormControl,
  FormGroupDirective
} from '@angular/forms';

@Component({
  selector: 'fe-input',
  template: `
    <mat-form-field>
      <input
        matInput
        [placeholder]="placeholder"
        [formControl]="control"
        [required]="required"
      />
      <mat-error>
        This is a required field!
      </mat-error>
    </mat-form-field>
  `,
  styles: [

  ]
})
export class FormElementInputComponent implements OnInit {
  // Values to be set:
  @Input() controlPath: any;
  @Input() placeholder: string;
  @Input() controlValue: any;
  @Input() required = false;

  // That's the reuseable control
  control: FormControl;

  constructor(private fgd: FormGroupDirective) {}

  ngOnInit() {
    this.control = this.fgd.control.get(
      this.controlPath
    ) as FormControl;
  }
}

И, наконец, это часть шаблона my-form-array:

 template: `
  <div fxLayout="column">
    <div *ngFor="let c of control.controls; index as i">

      <div [formGroupName]="i">

        <fe-input 
        [controlPath]="q"
        placeholder="Question"
        [required]="required"
      ></fe-input>

      <fe-input 
        [controlPath]="a"
        placeholder="Answer"
        [required]="required"
      ></fe-input>
      <div>

    </div>
  </div>
  `,

Ответы [ 2 ]

1 голос
/ 02 июля 2019

Во-первых, ваш FormArray - это FormArray для FormControls, которые получают в качестве значения объект, а не FormArray для FormGroup. Вы должны измениться при создании формы, как

this.newForm = this.fb.group({
  id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
  chapter: this.fb.group({
    name: ['Some Chapter', Validators.required]
  }),
  faq: this.fb.array(this.faqArray.map(x=>this.fb.group({
    q:x.q,
    a:x.a
  })))
}); 

Во-вторых, вы можете использовать «другой способ» для работы с FormArray FormGroup, чтобы он не использовал [formGroupName = "i"] else [formGroup] = "control". да

<!--remove this that not work
<div *ngFor="let c of control.controls; index as i">
      <div [formGroupName]="i">
           ....
      <div>
</div>
-->
<!--use [formGroup] -->
<div  *ngFor="let c of control.controls; index as i">
    <div [formGroup]="c">
          ....
    </div>
</div>

Ну, внутри formGroup вам нужны ваши fe-input [controlPath] = "'q'" и [controlPath] = "'a'"

Я удаляю группу форм, потому что она тоже неправильная, попробуйте использовать [formGroup] и использовать viewProviders: [{обеспечить: ControlContainer, useExisting: FormGroupDirective}]

Вы видите свой разветвленный стек

Обновление , если мы пометим массив массива с помощью

<my-form-array 
[controlPath]="['faq','a','q']"
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>

Мы можем изменить наш массив my-form-array так, в ngOnInit

this.control = this.fgd.control.get(
      this.controlPath[0]
    ) as FormArray;

и .html

   <div *ngFor="let c of control.controls; index as i">
      <div [formGroup]="c">
       <fe-input *ngFor="let fields of controlPath.slice(1)"
        [controlPath]="fields"
        placeholder="Question"
        [required]="required"
      ></fe-input>
    </div>

перебитый стек-блиц

0 голосов
/ 02 июля 2019

так что в вашем дочернем компоненте вы должны использовать formArrayName="faq", который вы будете получать от родительского компонента с помощью FormGroupDirective, а в массиве viewProviders дочернего компонента вы должны использовать useExisting, как это

viewProviders: [
    { provide: ControlContainer, useExisting: FormGroupDirective }
  ]

в вашем HTML

<div formArrayName="faq" *ngFor="let fa of faq['controls']; let faqIndex = index;">
    <div [formGroupName]="faqIndex">
// here goes your code
     </div>
</div>

...