Reputation: 1544
How do I use an AsyncPipe
inside an ngFor
or what is best practice to accomplish asynchronous calls for items of a list?
Example: Let's say I have an Order with Products and I want to display the product's manufacturer. But the manufacturer needs to be loaded from a storage (localstorage) in order to be displayed. My initial solution would've been the following (untested pseudo-code):
HTML:
<ol>
<li *ngFor="let prod of order.toProducts">
{{ prod.Name }}: {{ DisplayManufacturer(prod) | async }}
</li>
<ol>
TypeScript:
public async DisplayManufacturer(prod: product) {
let manufacturer = await this.storageService.Load(prod.manufacturerId);
if (manufacturer) {
return manufacturer.name + ' (' + manufacturer.address + ')';
}
return "";
}
This - of course - results in an endless loop.
My next solution would be, to go through all orders and all their products inside ngOnInit
to find the manufacturers and store them in a Map
for later use in the expression.
I feel there should be a better solution - as this is not the end yet: I would also have to implement DoCheck
to check if any of the products arrays or manufacturers change and reload said map. And this for every similarly referenced field.
Upvotes: 2
Views: 1230
Reputation: 7279
You do not need to use async/await or load all data before displaing it. Add a field to the product
type and assign a Promise or an Observable that would resolve to the required data.
service
Load(id: any): any {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Manufacturer id [${id}], name, address`);
}, Math.random() * 5000 + 2000);
});
}
component
ngOnInit() {
for (let i = 1; i < 10; i++) {
this.order.toProducts.push(
{ Name: 'ProdName' + i, manufacturerId: i, data: this.storageService.Load('manufacturerId#' + i) },
);
}
}
template
<ol>
<li *ngFor="let prod of order.toProducts">
{{ prod.Name }}: {{ prod.data | async }}
</li>
<ol>
Full working example: https://stackblitz.com/edit/angular-gpqvuu
Upvotes: 5
Reputation:
You get the products :
products$ = this.myService.getProducts();
Then the manufacturers, and join them to the products :
manufacturersAndProducts$ = this.products$.pipe(
switchMap(products => forkJoin(products
.map(product => this.myService.getManufacturerForProduct(product))
)).pipe(
map(manufacturers => products
.map((product, index) => ({ product, manufacturer: manufacturers[index] }))
)
)
);
You then use this into your HTML
<ng-container *ngIf="manufacturersAndProducts$ | async as list">
<div *ngFor="let item of list">
{{ item.product.name }} is made by {{ item.manufacturer.name }}
</div>
</ng-container>
But it is a bad example, because with HTTP calls you don't really need this kind of reactive programming. It is useful when, for instance, the list of products changes every 10s or so.
To answer your doubts, your question is out of context : it should not be about async, but about good practices when it comes to templates.
You should not use impure functions in your template (functions that have side effects), otherwise, as you said, your view get rendered again, and it results in infinite loops, or at best, performance issues.
You should either use a pure function, or construct a full object with all the data in it (like I've done here, but you don't need the async pipe)
Upvotes: 1