Reputation: 43
I have been trying to get data from Firebase (using Angular).
Program summary
It's a small app to store meal recipes, their ingredients to generate cumulated shopping list based on meal planning - daily, weekly, etc.
What I have:
1. Firebase database structure (fragment of it)
Ingredients (collection)
Id: string
Name: string
Tags: array of string
Recipes (collection)
CreatedOn: Timestamp
MealTitle: string
MealType: string
Method: string
RecipeIngredients (subcollection)
Id: string
IngredientId: string
ExtendedDescription: string
QTY: number
UOM: string
As you can see Recipes collection contains RecipeIngredients sub-collection which has IngredientId from Ingredients collection and some additional data.
2. Method for getting list of ingredients from RecipeIngredients sub-collection for specific recipe
getRecipeIngredients(recipeId: string): Observable<RecipeIngredient[]> {
this.recipesCollection = this.afs.collection('Recipes', ref => ref.orderBy('CreatedOn', 'desc'));
this.recipeIngredients = this.recipesCollection.doc(recipeId).collection('RecipeIngredients').snapshotChanges().pipe(
map(changes => {
return changes.map(action => {
const data = action.payload.doc.data() as RecipeIngredient;
data.Id = action.payload.doc.id;
return data;
})
})
)
return this.recipeIngredients;
}
3. Method for getting specific ingredient based on its Id
getIngredientById(ingredientId: string): Observable<Ingredient> {
this.ingredientDoc = this.afs.doc<Ingredient>(`Ingredients/${ingredientId}`);
this.ingredient = this.ingredientDoc.snapshotChanges().pipe(
map(action => {
if (action.payload.exists === false) {
return null;
} else {
const data = action.payload.data() as Ingredient;
data.Id = action.payload.id;
return data;
}
})
)
return this.ingredient;
}
4. What I want to achieve
I want to display a list of ingredients for specific recipe in a table containing columns: Ingredient Name, Extended Description, QTY, UOM. The thing is I cannot figure out how to get Ingredient name. Reasonable solution for me is to do it inside getRecipeIngredients an extend its returning result by that field or by whole Ingredients collection. I tried RxJS operators like mergeFlat or combineLatest but I got stuck.
I could of course change the database structure but that is also a learning project for me. I believe I'll have this kind of challenge sooner or later.
Upvotes: 4
Views: 440
Reputation: 872
This was an interesting one, but I think I have it worked out. Please check out my StackBlitz. This decision tree is usually good enough to provide you a function for most Observable scenarios, but I believe this one required a combination of functions.
Relevant code:
const recipeId = 'recipe1';
this.recipe$ = this.getRecipeIngredients(recipeId).pipe(concatMap(recipe => {
console.log('recipe: ', recipe);
const ingredients$ = recipe.recipeIngredients.map(recipeIngredient => this.getIngredientById(recipeIngredient.id));
return forkJoin(ingredients$)
.pipe(map((fullIngredients: any[]) => {
console.log('fullIngredients: ', fullIngredients);
fullIngredients.forEach(fullIngredient => {
recipe.recipeIngredients.forEach(recipeIngredient => {
if (recipeIngredient.id === fullIngredient.id) {
recipeIngredient.name = fullIngredient.name;
}
});
});
return recipe;
}));
})).pipe(tap(finalResult => console.log('finalResult: ', finalResult)));
I included the console logs to help illustrate the state of the data as it's piped around. The main idea here was to use concatMap
to tell the recipe Observable that I need to map it with another observable once this one is complete, then to use a forkJoin
to get the full representation of each recipe ingredient, and finally mapping those results back to the original recipe.
Upvotes: 1