如果 FormGroup 中的 FormArray 数据依赖于多个 Observable,如何显示这些数据?
How to display FormArray data in FormGroup if such data is dependent on several Observable?
我已经浪费了几天时间尝试显示异步动态加载的 FormArray 数据,但没有成功。
简而言之,我有 FormGroup
,它是在点击时创建的,并以模式 window 显示(显示在订阅条例实体详细信息 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
表单字段。而且我不知道如何在访问 ordinanceLanguage.controls
之前将数据从 ordinanceLanguages$
传输到 ordinanceLanguages
字段。我试过使用 ordinanceLanguages$
订阅,使用 ngIf="setOrdinanceLanguagesToForm(ol)
之类的直接赋值等。但没有任何工作正常 - 我要么根本不获取数据,要么获取无休止的输出,要么仅在 [= 之后获取正确的数据16=] 已满载..
<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 数据准备就绪后加载...
您应该在从 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}
)
}
}
我已经浪费了几天时间尝试显示异步动态加载的 FormArray 数据,但没有成功。
简而言之,我有 FormGroup
,它是在点击时创建的,并以模式 window 显示(显示在订阅条例实体详细信息 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
表单字段。而且我不知道如何在访问 ordinanceLanguage.controls
之前将数据从 ordinanceLanguages$
传输到 ordinanceLanguages
字段。我试过使用 ordinanceLanguages$
订阅,使用 ngIf="setOrdinanceLanguagesToForm(ol)
之类的直接赋值等。但没有任何工作正常 - 我要么根本不获取数据,要么获取无休止的输出,要么仅在 [= 之后获取正确的数据16=] 已满载..
<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 数据准备就绪后加载...
您应该在从 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}
)
}
}