I'm building an experimental application that uses Angular2, Firebase and AngularFire2.
This is what my data looks like:
{
"ShoppingCartItem":
{
"item1":{
"quantity": 2,
"productId": "abc"
},
"item2":{
"quantity": 1,
"productId": "bcd"
}
}
"Product"
{
"abc":{
"name": "product1",
"price": 5
},
"bcd":{
"name": "product2",
"price": 6
}
}
}
Below are my cart.ts
this.items = this.af.database.list('ShoppingCartItem')
.map(carts => {
return carts.map(item => {
item.product = this.af.database.object(`Product/${item.productId}`);
return item;
});
});
Below are my cart.html
<table>
<tbody>
<tr *ngFor="let item of (items | async)">
<td>{{(item.product | async)?.name}}</td>
<td><input type="text" name="quantity" [(ngModel)]="item.quantity" size="1" class="form-control"></td>
<td>{{(item.product | async)?.price | number: '.2'}}</td>
<td>{{(item.product | async)?.price * item.quantity | number: '.2'}}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2" class="text-right">
<strong>Total :</strong>
</td>
<td colspan="2" class="text-left">
?????????
</td>
</tr>
</tfoot>
I want to calculate the sum of the ShoppingCart. So I want to find the value of (2*5) + (1*6) = 16 as the data shows. How do i do it.
This plunker
The problem is in this code:
carts.map(cart => {
this.af.database.object(`Product/${cart.productId}`)
.subscribe(d => {
cart.product = d;
});
return cart;
});
carts.forEach(cartItem => {
qty += cartItem.quantity;
total += cartItem.quantity * cartItem.product.price;
// console.log(cartItem);
});
Two calls: carts.map
and carts.forEach
are synchronous and happen one after another. But loading of products (via af.database.object
) is asynchronous so when you iterating over cartItem products not been loaded yet.
The solution would be to chain product loading and total calculation. See here.
Since you are using Firebase, first thing I notice is key names. Fix them like this. Where cart.$key
is some hashcode e.g. -KXeML1Na9qCsvK4JSyQ
.
{
"ShoppingCartItem": {
-KXeML1OkDyUVTAdHYPx : {
-KXeML1OkDyUVTAdHYPx: true,
-KXeML1PP4faQG2Z3fzU: true
},
-KXeML1Na9qCsvK4JSyQ: {
-KXeML1PP4faQG2Z3fzU: true
}
},
"Products": {
-KXeML1OkDyUVTAdHYPx:{
"name": "product1",
"price": 5
},
-KXeML1PP4faQG2Z3fzU:{
"name": "product2",
"price": 6
}
}
}
Now rewrite your frontend logic. Please write and export suitable Product
class as well. Put below inside shoppingcart.service.ts
findProductKeysForCart(cartId: string): Observable<string[]> {
return this.database.list('ShoppingCartItem', {
query: {
orderByKey: true,
equalTo: cartId
}
})
//.do(console.log)// Check whats coming,
//.map(result => result[0])// might be needed
.map(sci => sci.map(product => product.$key));
}
findProductsForProductKeys(productKeys$: Observable<string[]>): Observable<Product[]> {
return productKeys$
.map(productKeys => productKeys.map(productKey => this.database.object(`Products/${productKey}`)))
.flatMap(prodObservables => Observable.combineLatest(prodObservables));
}
findAllProductsForCart(cartId): Observable<Product[]> {
//this fn gets your Products for a cart
return this.findProductsForProductKeys(this.findProductKeysForCart(cartId));
}
NOW, do your final calculations either in Product class or subscribe.
Below would go inside DisplayCart.component.ts
items;
constructor(private shoppingCartService: ShoppingCartService){}
ngOnInit(){
const items$ = this.shoppingCartService.findAllProductsForCart(this.cartId);
items$.subscribe(items => this.items = items);
}
You still need to complete the remaining stuff on your own, good luck.