Reputation: 1097
I'm following this tutorial and I'm stuck in this feature where I have to display a filtered array (object coming from firebase) when clicking an anchor. I followed all the implementation steps but I'm missing something...The code:
HTML:
<div class="row">
<div class="col-3">
<div class="list-group">
<a
*ngFor="let c of categories$ | async"
routerLink="/"
[queryParams]="{category: c.$key}"
class="list-group-item list-group-item-action"
[class.active]="category===c.$key">
{{c.name}}
</a>
</div>
</div>
<div class="col">
<div class="row">
<ng-container *ngFor="let p of filteredProducts; let i =index">
<div class="col">
<div class="card" style="width: 15rem;">
<img src="{{p.imageURL}}">
<div class="card-body">
<h5 class="card-title">{{p.title}}</h5>
<p class="card-text">{{p.price | currency: 'EUR': true}}</p>
<a href="#" class="btn btn-primary">Add to cart</a>
</div>
</div>
</div>
<div></div>
</ng-container>
</div>
</div>
</div>
TS class:
products:Product[]=[];
filteredProducts: Product[]=[];
category: string;
categories$;
constructor(
private productService: ProductService,
private categoryService: CategoryService,
private route: ActivatedRoute) {
this.categories$=this.categoryService.getCategories();
this.productService.getAll().subscribe(products =>this.products=products)
this.route.queryParamMap.subscribe(params =>{
this.category = params.get('category');
this.filteredProducts = (this.category) ?
this.products.filter(p=>p.category===this.category) :
this.products;
});
}
The service retrives data correcly, but when I log the filtered array, I get nothing...Can someone give me a hand?
Upvotes: 0
Views: 69
Reputation: 29335
your attempting to set your array before you retrieve the products. subscribe is an asynchronous operation so it doesn't execute in the order it's started, but rather in the order the results arrive, and queryParams is a type of observable that emits immediately, so that subscribe is executing BEFORE the products arrive, instead, use observable combineLatest and the map operator and the async pipe:
filteredProducts$: Observable<Product[]>;
....
const category$ = this.route.queryParamMap.pipe(map(params => params.get('category')));
this.filteredProducts$ = combineLatest(this.productService.getAll(), // combine the streams
category$)
.pipe(
tap(([products, category])=> console.log(products, category)), // this is how you log in a stream
map(([products, category]) => // you'll have the latest value from both
(category) ? // run your filter
products.filter(p => p.category.toLowerCase() === category.toLowerCase()) :
products),
tap(filteredProducts => console.log(filteredProducts)) // this is how you log in a stream
);
then in your template just use the async pipe like you did with categories$
<ng-container *ngFor="let p of filteredProducts$ | async; let i =index">
an Observable is JUST a definition of how to handle a stream of data. logging things outside of that stream will not show you the values inside of that stream. the tap
operator exists to allow you to log things inside of the stream
Upvotes: 1