Problem: I'm attempting to retrieve information from Firebase with a second function which relies on data that was set from a prior function call. However, the second function executes before the first has set the data. I know this is because of how functions execute in Typescript / Angular, but i'm not very familiar working with asynchronous functions.
Question: What is the best way to make the second function wait until the data it needs is set?
Additional Info: The data is being stored / retrieved from Firebase Firestore. The collection im working with contains any number of city documents. Each of these city documents contains a collection of families. Since the number of cities can vary, I need to first retrieve a list of cities that have families, then use that list to retrieve the families inside. I've attempted to use Promises to fix the issue, tried making the functions asynchronous (then using await
), and making callback functions, but have had no luck. I've included my attempted solutions below. I there is any more code that I need to include (or if I need to post the Firestore layout) please let me know.
I'm also open to other solutions in retrieving / storing the data as long as it follows the same format as the Firestore data.
Code:
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 };
}))
);
}
}
Attempted solutions: I've tried the following solutions but neither have worked thus far:
With promises:
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();
});
}
With asynchronous:
async ngOnInit() {
await this.getActiveCities();
await this.getAllFamilies();
}
With callbacks I attempted something similar to: https://stackoverflow.com/a/21518470/5785332
I've also tried to implement solutions from answers to similar question: async/await in Angular `ngOnInit`