Вложенный пользовательский компонент FormArray не связывается с дочерней формой с FormArrayName

Я попытался иметь 2 вложенные формы, используя CVA. проблема в том, что второй из не инициализируется данными, когда я привязываю его к formControl.

Stackblitz

Вложенный пользовательский компонент FormArray не связывается с дочерней формой с FormArrayName

У меня есть ГЛАВНАЯ ФОРМА:

this.requestForm = this.fb.group({
  garageId: 0,
  routes: new FormArray([
    new FormGroup({
      addressPointId: new FormControl,
      municipalityId: new FormControl,
      regionId: new FormControl,
      rvId: new FormControl,
      sequenceNumber: new FormControl,
      settlementId: new FormControl,
      regionName: new FormControl,
      municipalityName: new FormControl,
      settlementName: new FormControl,
      description: new FormControl,
    })
  ]),
  endDateTime: 0,
});

В html основной формы я связываю маршруты с помощью formArrayName.

 <app-cva-form-array formArrayName = "routes"></app-cva-form-array>

Компонент CVA-ФОРМА-МАССИВ имеет.

form = new FormArray([
new FormGroup({
  addressPointId: new FormControl,
  municipalityId: new FormControl,
  regionId: new FormControl,
  rvId: new FormControl,
  sequenceNumber: new FormControl,
  settlementId: new FormControl,
  regionName: new FormControl,
  municipalityName: new FormControl,
  settlementName: new FormControl,
  description: new FormControl,
})
]);

Отсюда все работает нормально. Я привязываю каждую группу форм в массиве к дочернему компоненту CVA-FORM.

<app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>

CVA-ФОРМА для каждой formGroup я создал отдельный компонент на случай, если я хочу использовать сам компонент, а не весь массив.

  form: FormGroup = new FormGroup({
    regionName: new FormControl,
    regionId: new FormControl,
    municipalityName: new FormControl,
    municipalityId: new FormControl,
    sequenceNumber: new FormControl,
    settlementName: new FormControl,
    settlementId: new FormControl,
    addressPointId: new FormControl,
    description: new FormControl,
    rvId: new FormControl,
  });

привязка main-form <--to--> app-cva-form-array по какой-то причине не работает.

Идея этих форм исходит от выступление Кары на angulaconnect.вот ее слайды.

помогите плз!

Мне непонятно, вы спрашиваете о форме фона?

Prashant Pimpale 10.04.2019 09:42

да. если вы посмотрите на jsons. вы увидите, что основная форма (фоновая форма) не синхронизирована с другими.

Vato 10.04.2019 09:43

Посмотрите на ответ posetd

Prashant Pimpale 10.04.2019 10:13
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
2
3
6 409
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вам необходимо передать обновленные данные формы из дочернего компонента в родительский компонент. Я использовал метод this.form.valueChanges() для обнаружения изменений, а затем передал значение формы родительскому компоненту.

Parent Component:

HTML-код:

<app-cva-form-array formArrayName = "routes" (onChildFormValueChange) = "onFormChange($event)"></app-cva-form-array>

Код ТС:

public onFormChange(form): void {
    this.requestForm = form;
}

Child Component:

HTML-код:

No change:)

Код ТС:

@Output() onChildFormValueChange: EventEmitter<any> = new EventEmitter<any>();

registerEvent() {
    this.form.valueChanges.subscribe(() => {
      this.onFormValueChange()
    });
}

public onFormValueChange(): void {
    this.onChildFormValueChange.emit(this.form);
}

и вызовите метод registerEvent в конструкторе, например:

constructor(){
  this.registerEvent();
}

Working_Stackblitz

это дает только одностороннюю привязку. хотя я знаю, как сделать 2 способа, это все еще не то, что я ищу. если вы проверите, как cva-form-array привязывается к cva-form, я хочу добиться аналогичной привязки для main-form <--> cva-form-array. цель использования cva — упростить код и не использовать лишние функции.

Vato 10.04.2019 10:21

Я добавил внешние ресурсы о происхождении кода внизу вопроса.

Vato 10.04.2019 10:23

@Vato Нет, это двусторонняя привязка

Prashant Pimpale 10.04.2019 13:18

Если вы измените основную форму, массив cva-form не изменится. если вы пишете regionName: new FormControl('asdfa'), в основной форме он оставляет имена областей дочерних компонентов нулевыми.

Vato 10.04.2019 15:03
stackblitz.com/edit/angular-b6nm8c здесь, если вы добавляете массив из родителя, он не привязывается к дочернему, только наоборот. но в любом случае, как я уже сказал, ни одна из функций не должна требоваться при использовании cva.
Vato 10.04.2019 15:21

Я считаю, что проблема здесь в том, что formArrayName не является входом для NG_VALUE_ACCESSOR/DefaultValueAccessor.

Также обратите внимание:

Her examples are static parent->multiple children... meaning 1 to many and not dynamic. You are attempting static parent to many dynamic child->grandChild relationships built from formArray, then trying to dynamically link grandChild form to parent formArrayIndex that was passed through the child to the grandChild. Your stackblitz deviates from the structure she is teaching, and certainly introduces some new challenges not covered in the lecture.

Изучение того, как перебирать FormArray на уровне parent и создавать экземпляры отношений child->grandchild внутри этого цикла, может быть возможным решением, таким образом, вы не передаете весь массив вниз, а только formGroup, который будет применяться.

<h1>MAIN FORM</h1>
    {{ requestForm.value | json }}
    <div *ngFor = "let route of requestForm.get('routes').controls">
        <app-cva-form-array formControl = "route" (onChildFormValueChange) = "onFormChange($event)"></app-cva-form-array>
    </div>

Селекторы

  • Вход: не ([тип = флажок]) [имяКонтроляФормы]
  • текстовая область[имяКонтроляФормы]
  • Вход: не ([тип = флажок]) [формаКонтроль]
  • текстовая область [форма управления]
  • Вход: не ([тип = флажок]) [нгМодель]
  • текстовая область [ngModel]
  • [ngDefaultControl]

https://angular.io/api/forms/DefaultValueAccessor#selectors


Единственными вариантами ввода являются formControlName, formControl, ngModel и ngDefaultControl...

This is the reason formArrayName will not work in main-form <--> cva-form-array however, formControlwill work for child-child to child level, as you are passing a singular formControl into your app-cva-form, from your app-cva-form-array via the *ngFor loop.

<mat-card *ngFor = "let route of getForm.controls;
   <app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>

Я считаю, что ключ к пониманию здесь заключается в том, что formArray — это всего лишь организационный контейнер для своих дочерних элементов... он не будет делать то, что вы хотите, в этом сценарии без помощи дополнительной логики.

There currently doesn't appear to be the necessary functionality to accept formArray as an input, iterate/dynamically manage the array, and link changes back to the parent formArray.

Ответ принят как подходящий

Когда вы используете «настраиваемый элемент управления формой», вам необходимо учитывать, что вы загружаете элемент управления формой cursom с помощью элемента управления формы (не FormArray, не FormGroup). FormControl имеет в качестве значения массив или объект, но вам не нужно смущаться по этому поводу. (*)

Посмотреть в работе можно в стекблиц

Это твоя форма, как

//in main.form
this.requestForm = new FormGroup({
  garageId: new FormControl(0),
  routes: new FormControl(routes), //<--routes will be an array of object
  endDateTime: new FormControl(0)
})

//in cva-form-array
this.form=new FormArray([new FormControl(...)]); //<-this.form is a 
                             //formArray of FormControls NOT of formGroup

//finally in your cva-form
this.form=new FormGroup({});
this.form=formGroup({
      addressPointId: new FormControl(),
      municipalityId: new FormControl(),
      ...
})

Я создал константу для экспорта просто в код. МОЙ постоянный экспорт

export const dataI = {
  addressPointId: "",
  municipalityId: "",
  regionId: "",
  rvId: "",
  sequenceNumber: "",
  settlementId: "",
  regionName: "",
  municipalityName: "",
  settlementName: "",
  description: "",
}

Итак, в mainForm у нас есть

  ngOnInit() {
    let routes:any[]=[];
    routes.push({...dataI});
    this.requestForm = new FormGroup({
      garageId: new FormControl(0),
      routes: new FormControl(routes),
      endDateTime: new FormControl(0)
    })
  }
<mat-card [formGroup] = "requestForm" style = "background: #8E8D8A">
    <app-cva-form-array formControlName = "routes"></app-cva-form-array>
</mat-card>

В массиве cvc-form создайте formArray, когда мы дадим значение

  writeValue(v: any) {
    this.form=new FormArray([]);
    for (let value of v)
        this.form.push(new FormControl(value))

    this.form.valueChanges.subscribe(res=>
    {
      if (this.onChange)
        this.onChange(this.form.value)
    })
  }

    <form [formGroup] = "form" >
        <mat-card *ngFor = "let route of form.controls; 
            let routeIndex = index; let routeLast = last;">
           <button (click) = "deleteRoute(routeIndex)">
             cancel
           </button>
           <app-cva-form [formControl] = "route" (blur) = "onTouched()"></app-cva-form>
      </form>

Наконец, cva-форма

  writeValue(v: any) {
    this.form=new FormGroup({});
    Object.keys(dataI).forEach(x=>{
      this.form.addControl(x,new FormControl())
    })

    this.form.setValue(v, { emitEvent: false });
    this.form.valueChanges.subscribe(res=>{
       if (this.onChanged)
        this.onChanged(this.form.value)
    })
  }

<div [formGroup] = "form">
  <mat-form-field class = "locationDate">
    <input formControlName = "regionName">
    <mat-autocomplete #region = "matAutocomplete" 
      (optionSelected) = "selectedLocation($event)">
      <mat-option *ngFor = "let region of regions" 
      [value] = "region">
        {{region.regionName}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
  <mat-form-field class = "locationDate">
    <input formControlName = "municipalityName" 
      [matAutocomplete] = "municipality"
      (blur) = "onTouched()"
      [readonly] = "checked || this.form.value.regionId < 1">
   ....
   </form>

(*) Да, мы привыкли видеть, что FormControl имеет в качестве значения строку или число, но никто не запрещает нам, чтобы значение было объектом или массивом (например, ng-bootstrap DatePicker хранит объект {year : .. месяц: .., день ..}, mat-multiselect хранит массив, ...)

Обновлять Конечно, мы можем передать нашему элементу управления данные из службы или чего-то подобного. Единственное, что мы должны учитывать, это то, как мы предоставляем данные. Как обычно, мне нравится создавать функцию, которая получает данные или ноль и возвращает FormControl.

  getForm(data: any): FormGroup {
    data = data || {} as IData;
    return new FormGroup({
      garageId: new FormControl(data.garageId),
      routes: new FormControl(data.routes),
      endDateTime: new FormControl(data.endDateTime)
    })
  }

где IData — это интерфейс

export interface IData {
  garageId: number;
  routes: IDetail[];
  endDateTime: any
}

и IDetail другой интерфейс

export interface IDetail {
  addressPointId: string;
  ...
  description: string;
}

Тогда у нас могут быть сложные данные, например (извините за большой объект)

let data = {
  garageId: 1,
  routes: [{
    addressPointId: "adress",
    municipalityId: "municipallyty",
    regionId: "regionId",
    rvId: "rvId",
    sequenceNumber: "sequenceNumber",
    settlementId: "settlementId",
    regionName: "regionName",
    municipalityName: "municipalityName",
    settlementName: "settlementName",
    description: "description",
  },
  {
    addressPointId: "another adress",
    municipalityId: "another municipallyty",
    regionId: "another regionId",
    rvId: "another rvId",
    sequenceNumber: "another sequenceNumber",
    settlementId: "another settlementId",
    regionName: "another regionName",
    municipalityName: "another municipalityName",
    settlementName: "another settlementName",
    description: "another description",
  }],
  endDateTime: new Date()
}

Тогда только нужно сделать

this.requestForm = this.getForm(data);

Stackblitz, если он обновлен

Спасибо за ответ! я могу использовать интерфейс вместо константы? Я не хочу, чтобы массив форм был привязан к основной форме. Или вы думаете, что я должен иметь const в другом файле?

Vato 15.04.2019 10:35

привет, я попытался поместить решение в свой проект. Я пытаюсь загрузить исходные данные, которые уже существуют. Я следовал тому же коду, что и ваш. но я заметил, что когда я инициализирую данные, они не передаются дочерним элементам, а когда я пытаюсь добавить другие маршруты, он ставит индекс перед объектами массива. например, "маршруты": { "0": { "regionId": "" ... "municipalityId": "" } }. это может быть проблемой и с инициализацией. знаете какое может быть решение?

Vato 15.04.2019 14:26

@Vato, я обновил ответ и stackblitz, надеюсь, это поможет. Насчет использования интерфейса или константы я пробовал, но безрезультатно :(, но конечно интерфейсы и константы могут быть вне основной формы

Eliseo 15.04.2019 20:07

@Eliseo, спасибо, отлично работает. но он по-прежнему добавляет индекс перед массивами после добавления адреса. он делает массивы { 0: {} 1: {} ... } и т. д., прежде чем вы добавите массив, который выглядит нормально [{ }, { }, { }]

Vato 16.04.2019 08:07

неважно. Я удалил распространение {...} из onChange, и он добавляется в массив без индексов. Я еще не знаком с распространением, поэтому это меня смутило. Спасибо за помощь!

Vato 16.04.2019 09:40

Я рад, что вы нашли "ошибку"

Eliseo 16.04.2019 11:17

Подумав над проблемой, реально можно убрать вообще this.onChange, в stackblitz.com/edit/angular-6seas7?file=src/app/cva-form-arr‌​ay/… (forket stackblitz) удаляю и смотрю как работает

Eliseo 16.04.2019 11:43

@Eliseo по какой-то причине я до сих пор не могу предварительно загрузить данные из сервиса. Я получаю каждый маршрут с сервера и нажимаю их на route.push({...route}), но когда данные попадают в массив cva-form-array, они не проходят через цикл writeValue, где вы добавляете их в форму. Я заметил, что он не входит в цикл for (const value of v). В первом случае, когда я добавляю dataI, я печатаю v в writeValue и получаю [{...}] в консоли, но когда я пишу новые данные с сервера поверх dataI, в writeValue печатается []. Оба массива состоят из объектов одного типа, когда я расширяю их в инструментах браузера, но сначала это просто пустые значения ' '.

Vato 17.04.2019 09:46

в чем разница между [] и [{...}] и как это может повлиять на цикл for (const value of v)? Извините, что снова беспокою.

Vato 17.04.2019 09:47
stackoverflow.com/questions/55722761/… Я задал очередной вопрос о том, что написал в топе. Я включаю свой сервисный вызов туда.
Vato 17.04.2019 10:06

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

Vato 17.04.2019 15:14

единственное, что теперь я получаю ExpressionChangedAfterItHasBeenCheckedError. что не так уж и плохо. Я постараюсь решить это.

Vato 17.04.2019 15:45

чтобы избежать ExpressionChanged... иногда полезно использовать setTimeOut(()=>{...your code..})

Eliseo 17.04.2019 16:04

Я предполагаю, что вложенное состояние проверки FormControls/FormGroups не может быть отражено в форме верхнего уровня при использовании этого метода. Я ошибся ? Было бы здорово распространить состояние проверки элементов управления app-cva-form на основную форму.

Aloene 03.03.2020 18:11

Другие вопросы по теме