Mellville
Mellville

Reputation: 1097

Angular - Rendering filtered array issue

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?

EDIT: enter image description here

Upvotes: 0

Views: 69

Answers (1)

bryan60
bryan60

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

Related Questions