问题:我正在尝试使用第二个函数从 Firebase 检索信息,该函数依赖于从先前函数调用中设置的数据。但是,第二个函数在第一个函数设置数据之前执行。我知道这是因为函数在 Typescript / Angular 中的执行方式,但我不太熟悉使用异步函数。
问题:让第二个函数等到设置所需数据的最佳方法是什么?
附加信息:正在从 Firebase Firestore 存储/检索数据。我正在使用的集合包含任意数量的城市文件。这些城市文件中的每一个都包含一系列家庭。由于城市的数量可能会有所不同,我需要首先检索有家庭的城市列表,然后使用该列表检索其中的家庭。我尝试使用 Promises 来解决问题,尝试使函数异步(然后使用await
),并制作回调函数,但没有运气。我在下面包含了我尝试过的解决方案。我还有更多需要包含的代码(或者如果我需要发布 Firestore 布局)请告诉我。
我也对检索/存储数据的其他解决方案持开放态度,只要它遵循与 Firestore 数据相同的格式。
代码:
home.component.ts:
export class HomeComponent implements OnInit {
activeCities: any = [];
activeFamilies: Map<string, Family[]>;
constructor(private fbService: FirebaseService) { }
ngOnInit() {
this.getActiveCities();
this.getAllFamilies();
}
getActiveCities() {
this.fbService.getActiveCities().subscribe(
data => {
this.activeCities = data;
},
error => {
console.log("Error retrieving active cities");
}
);
}
getAllFamilies() {
for (let city of this.activeCities) {
this.fbService.getFamiliesForCity(city.id).subscribe(
data => {
let families: Family[] = [];
families = data;
this.activeFamilies.set(city .id, families);
},
error => {
console.log("Error retrieving families for active cities");
}
);
}
}
}
firebase.service.ts:
export class FirebaseService {
private activeCitiesPath = '/active_cities';
constructor(private firestore: AngularFirestore) { }
getActiveCities() {
let colRef: AngularFirestoreCollection<any>;
let temp: Observable<any[]>;
let path = this.activeCitiesPath;
colRef = this.firestore.collection(path);
return colRef.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as any;
const id = a.payload.doc.id;
return { id, ...data };
}))
);
}
getFamiliesForCity(cityCode: string) {
let colRef: AngularFirestoreCollection<any>;
let temp: Observable<any[]>;
let path = this.activeCitiesPath + "/" + cityCode + "/families";
colRef = this.firestore.collection(path);
return colRef.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as any;
const id = a.payload.doc.id;
return { id, ...data };
}))
);
}
}
尝试的解决方案:我尝试了以下解决方案,但到目前为止都没有奏效:
承诺:
async ngOnInit() {
let promises: Promise<void>[] = [];
promises.push(this.getActiveCities());
promises.push(this.getAllFamilies());
Promise.all(promises).then(() => {
console.log("All promises worked");
}).catch(() => {
console.log("Error in promise");
});
}
private getActiveCities(): Promise<void> {
return new Promise<void>((resolve, reject) => {
//same code but adding resolve(); and reject();
});
}
private getAllFamilies(): Promise<void> {
return new Promise<void>((resolve, reject) => {
//same code but adding resolve(); and reject();
});
}
使用异步:
async ngOnInit() {
await this.getActiveCities();
await this.getAllFamilies();
}
通过回调,我尝试了类似于:https ://stackoverflow.com/a/21518470/5785332
我还尝试从类似问题的答案中实现解决方案:async/await in Angular `ngOnInit`
最简单的解决方案是在您的第一个订阅中调用您的第二个函数:
ngOnInit() {
this.getActiveCities();
}
getActiveCities() {
this.fbService.getActiveCities().subscribe(
data => {
this.activeCities = data;
this.getAllFamilies();
},
error => {
console.log("Error retrieving active cities");
}
);
}
尽管更好的设计是将所有内容都保留为可观察对象并使用async
html 中的管道订阅。
export class HomeComponent implements OnInit {
constructor(private fbService: FirebaseService) { }
activeFamiliesMap = new Map<string, Observable<Family[]>>();
activeCities$: Observable<any[]> = this.fbService.getActiveCities().pipe(
tap((activeCities) => {
for (const city of activeCities) {
this.activeFamiliesMap.set(city.id, this.activeFamilies(city.id));
}
}),
catchError((err) => {
console.error('Error retrieving active cities', err);
return of([]);
})
);
activeFamilies(id: any): Observable<Family[]> {
return this.fbService.getFamiliesForCity(id).pipe(
catchError((err) => {
console.error('Error retrieving families for city id:', id, err);
return of([]);
})
);
}
}
只是如何显示数据的示例:
<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>
<ng-container *ngFor="let city of activeCities$ | async">
<div>City Id: {{ city.id }}</div>
<div>Families:</div>
<pre>{{ activeFamiliesMap.get(city.id) | async | json }}</pre>
</ng-container>
堆栈闪电战: https ://stackblitz.com/edit/angular-ivy-trkuqx ?file=src/app/app.component.ts
更好的设计可能是让您的服务返回地图的可观察对象,尽管它是可观察对象的丑陋野兽。至少它隐藏了组件的逻辑:
服务
getFamilyMap(): Observable<Map<string, Family[]>> {
return this.getActiveCities().pipe(
map((activeCities) => {
return activeCities.map((city) => {
return this.getFamiliesForCity(city.id).pipe(
map((families) => {
return { id: city.id, families };
})
);
});
}),
switchMap((data) => forkJoin(data)),
map((data) => {
const res = new Map<string, Family[]>();
for (const entry of data) {
res.set(entry.id, entry.families);
}
return res;
}),
catchError((err) => {
console.error('Error retrieving family map', err);
return of(new Map<string, Family[]>());
})
);
}
零件
export class HomeComponent {
constructor(private fbService: FirebaseService) {}
activeCities$ = this.fbService.getActiveCities();
familyMapEntries$ = this.fbService
.getFamilyMap()
.pipe(map((map) => Array.from(map)));
}
我使用Array.from()
而不是map.entries()
因为迭代器倾向于抛出changedAfterChecked
错误。
html
<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>
<ng-container *ngFor="let entry of (familyMapEntries$ | async)">
<div>City Id: {{ entry[0] }}</div>
<div>Families:</div>
<pre>{{ entry[1] | json }}</pre>
</ng-container>
堆栈闪电战: https ://stackblitz.com/edit/angular-ivy-ffzpya ?file=src/app/firebase.service.ts
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句