вложенный пользовательский компонент FormArray не связывается с дочерней формой с FormArrayName - PullRequest
0 голосов
/ 08 апреля 2019

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

Stackblitz

enter image description here

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

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-ARRAY имеет.

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

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

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

CVA-FORM для каждой группы 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,
  });

Привязка app-cva-form-массива main-form <- to -> почему-то не работает.

Идея 1039 * Вот ее слайды.

help plz!

из этих форм.

Ответы [ 3 ]

4 голосов
/ 14 апреля 2019

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

Вы можете увидеть в работе stackblitz

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

//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(),
      ...
})

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

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 хранит объект {год: .. месяц: .., день ..}, 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);

Стек, если обновлено

1 голос
/ 11 апреля 2019

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

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

Ее примеры статичны parent->multiple children ..., что означает 1 для многих и не является динамическим. Вы пытаетесь статически parent ко многим динамическим child->grandChild отношениям, построенным из formArray, затем пытаемся динамически связать grandChild форму с родителем formArrayIndex, которая была передана через child к grandChild. Ваш стековый блик отличается от структуры, которой она обучает, и, безусловно, вводит некоторые новые проблемы, не затронутые в лекции.

Изучение того, как перебрать 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>

селекторы

  • ввод : нет ([type = checkbox]) [formControlName]
  • TextArea [formControlName]
  • ввод : нет ([type = checkbox]) [formControl]
  • TextArea [FormControl]
  • ввод : нет ([type = checkbox]) [ngModel]
  • TextArea [ngModel]
  • [ngDefaultControl]

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


Ваши варианты ввода: formControlName, formControl, ngModel и ngDefaultControl ...

Именно по этой причине formArrayName будет не работать в main-form <--> cva-form-array, однако formControl будет работать child-child to child level, когда вы передаете единственное число formControl в ваш app-cva-form, из вашего app-cva-form-array через цикл *ngFor.

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

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

Там в настоящее время, кажется, нет необходимой функциональности принять formArray в качестве ввода, выполнить итерацию / динамическое управление массивом, и ссылка переходит обратно к родителю formArray.

0 голосов
/ 10 апреля 2019

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

Родительский компонент:

HTML-код:

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

Код TS:

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

Дочерний компонент:

Код HTML:

No change:)

Код TS:

@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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...