首页 > 解决方案 > 如果这些数据依赖于几个 Observable,如何在 FormGroup 中显示 FormArray 数据?

问题描述

我浪费了几天时间试图显示异步动态加载的 FormArray 数据,但没有运气。简而言之,我有FormGroup它是在单击时创建并显示在模式窗口中的(显示在订阅条例实体详细信息 API 调用中加载的数据没有问题(数据被加载到selectedOrdinance对象中):

      buildOrdinanceForm() {
        this.ordinanceForm = this.fb.group({
          ordinanceId: this.selectedOrdinance.ordinanceId // already available, loaded via API call to Ordinance details page in service observable 'subscribe'
          ...
          ordinanceLanguages: new FormArray([]) // trouble here - data needs to be loaded asynchronously using another API
        });
        ...
      }
    
     get ordinanceLanguages$(): Observable<FormArray> {
        return combineLatest([
          this.languageCodesLookupData$,
          of(this.selectedOrdinance)
        ])
          .pipe(
            map(([lookupLanguages, selectedOrdinance]) => {
              const ordinanceLanguageFormArray = new FormArray(
                lookupLanguages
                  .map(x => new FormGroup({
                    ordinanceId: new FormControl(),
                    languageCode: new FormControl(x.id),
                    ordinanceAbbreviation: new FormControl({ value: '' }, [Validators.maxLength(20), Validators.required]),
                    ...
                  })
                ));
              ordinanceLanguageFormArray.controls.forEach(item => {
                const matchingLanguage = selectedOrdinance.ordinanceLanguages?.find(
                  x => x.ordinanceId === selectedOrdinance.ordinanceId
                    && x.languageCode === item.get('languageCode').value);
                item.patchValue({
                  ordinanceId: selectedOrdinance.ordinanceId,
                  ordinanceAbbreviation: matchingLanguage?.ordinanceAbbreviation,
                  ...
                });
              });
              return ordinanceLanguageFormArray;
            }),
            catchError(error => of(new FormArray([])))
          );
      }

OrdinanceLanguages导致麻烦:它依赖于已经可用的字段(selectedOrdinance对象),也依赖于异步加载的 Observable 数据(languageCodesLookupData$)。ordinanceLanguages加载完成后我无法显示。这里的问题是 [formGroupName] 指令需要ordinanceLanguages填写表单字段。而且我不知道如何在访问之前将数据从字段传输ordinanceLanguages$到. 我试过使用订阅,使用类似的东西直接分配。但没有什么能正常工作 - 我要么根本没有得到数据,要么得到无穷无尽的输出,要么只有在完全加载后才能得到正确的数据..ordinanceLanguagesordinanceLanguage.controlsordinanceLanguages$ngIf="setOrdinanceLanguagesToForm(ol)languageCodesLookupData$

      <ng-container *ngIf="(languageCodesLookupData$ | async) && (ordinanceLanguages$ | async) as ol">
         <ng-container formArrayName="ordinanceLanguages" *ngFor="let item of ordinanceLanguages.controls; let i = index;">
           <div [formGroupName]="i">
             <div class="form-group row">...</div>
             </div>
           </ng-container>
      </ng-container>

请看一下测试示例: https ://stackblitz.com/edit/angular-ivy-nnosbf?file=src/app/app.component.ts

我需要实现的是ordinanceLanguages一旦准备好就加载 FormArray 数据......

标签: angularobservableformarray

解决方案


您应该在从 API 获取数据后简单地延迟表单创建,或者在检索到数据后重新创建表单。

我已经简化了一点你的代码,所以不再有这样的混乱Observables。我还删除了表单修补步骤 - 只需立即创建具有适当值的表单。现在,对案件至关重要的所有内容都包含在buildOrdinanceForm(). https://stackblitz.com/edit/angular-ivy-gapevo?file=src/app/app.component.ts

import { ChangeDetectorRef, Component, OnInit, VERSION } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, of, debounceTime, combineLatest, map, catchError, delay } from 'rxjs';

export interface Ordinance {
  ordinanceId: number;
  ordinanceLanguages: OrdinanceLanguage[];
}

export interface OrdinanceLanguage {
  ordinanceId: number;
  languageCode: string;
  ordinanceAbbreviation: string;
}

export interface Lookup<T> {
  id: T;
  displayName: string;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  ordinanceForm: FormGroup;
  selectedOrdinance: Ordinance;
  open: boolean;
  isLoading=true;
  constructor(private fb: FormBuilder,private cdr:ChangeDetectorRef) {}

  get ordinanceLanguages(): FormArray {
    return this.ordinanceForm.get('ordinanceLanguages') as FormArray;
  }

  getLanguageLookupDataApiCall(): Observable<Lookup<string>[]> {
    const langs = [] as Lookup<string>[];
    langs.push({ id: 'E', displayName: 'English' });
    langs.push({ id: 'I', displayName: 'Italian' });
    langs.push({ id: 'F', displayName: 'French' });
    langs.push({ id: 'G', displayName: 'German' });
    return of(langs).pipe(delay(1000));
  }

  ngOnInit(): void {
  }

  openDetails() {
    this.open = true;
    this.constructOrdinance();
    this.buildOrdinanceForm();
  }

  closeDetails() {
    this.open = false;
  }

  constructOrdinance() {
    this.selectedOrdinance = {
      ordinanceId: 1,
      ordinanceLanguages: [
        { languageCode: 'E', ordinanceId: 1, ordinanceAbbreviation: 'EN-1' },
        { languageCode: 'G', ordinanceId: 1, ordinanceAbbreviation: 'DE-1' }
      ]
    } as Ordinance;
  }

  buildOrdinanceForm() {
    this.isLoading=true;
    this.getLanguageLookupDataApiCall().subscribe(lookupLanguages=>{
      const ordinanceLanguageFormArray = new FormArray(
        lookupLanguages
          .map(lang => {
            const matchingLang=this.selectedOrdinance.ordinanceLanguages?.find(
              x => x.ordinanceId === this.selectedOrdinance.ordinanceId
                && x.languageCode === lang.id);
                console.log(matchingLang);
            return new FormGroup({
            ordinanceId: new FormControl(this.selectedOrdinance.ordinanceId),
            languageCode: new FormControl(lang.id),
            ordinanceAbbreviation: new FormControl(matchingLang?.ordinanceAbbreviation ?? '' ,[Validators.maxLength(20), Validators.required])});
          })
        );   
      this.ordinanceForm = this.fb.group({
        ordinanceId: [
          typeof this.selectedOrdinance.ordinanceId === 'number'
            ? this.selectedOrdinance.ordinanceId
            : null
        ],
        ordinanceLanguages: ordinanceLanguageFormArray
      });
      console.log(this.ordinanceForm.value);
    },err=>console.error(err),()=>{this.isLoading=false}
    )

  }
}

推荐阅读