A guide on sorting through categories in Angular 9

Having trouble filtering categories in a Webshop? I've been following a tutorial by Mosh but I can't seem to get it right. No error messages but nothing is being filtered or displayed. I'm brand new to Angular and/or typescript, so please be patient with me. I've tried to fix this on my own for ages with no luck. Can anyone help me out?

Here's the code I have:

products.component.ts:

export class ProductsComponent {

 products: Product[]= [];
 filteredProducts: Product[] = [];
categories$: Observable<any>;
category: string;
products$: Observable<any>;


 constructor(
    route: ActivatedRoute,
    productService: ProductService, 
    categoryService: CategoryService) {
    this.filteredProducts;

productService.getAll().subscribe (products => this.products);



//Shows Categories in View
    this.categories$ = categoryService.getAll();
    route.queryParamMap.subscribe(params => {
      this.category = params.get('category');
      this.categories$.subscribe(res => console.log (res)); //Console.log  5 Categories Observable



      //Setting the filtered Products Array
      this.filteredProducts = (this.category) ? 
       this.products.filter(p => {
         return p.category === this.category;
       }) : 
      this.products;
      console.log(this.filteredProducts); // Console.log FilteredProducts Array
    });

   }

 }

products.component.html:

<div class="row">
  <div class="col-3">
    <div class="list-group">


      <!-- Bei queryParams c.name auswählbare Kategorien / bei c.key nicht sowie bei c.$key nicht-->
      <a *ngFor="let c of categories$ | async "
      routerLink= "/"
      [queryParams]="{ category: c.name }"  
       class="list-group-item list-group-item-action"
       [class.active]="category === c.name">
        {{ c.name }}
      </a>
    </div>  
  </div>
  <div class="col-9">
    <div class="row">
      <ng-container *ngFor="let product of filteredProducts ; let i = index">
        <div class="col">
          <div class="card" style="width: 15rem;">
          <img [src]="product.imageUrl" class="card-img-top" alt="Card image cap">
          <div class="card-body">
            <h5 class="card-title">{{ product.title }}</h5>
            <p class="card-text">{{ product.price | currency: 'EUR' }}</p>
            <a href="#" class="btn btn-primary">Add to Cart</a>
          </div>
        </div>
      </div>
      <div *ngIf="(i + 1) % 2 === 0" class="w-100"></div>
      </ng-container>
    </div>
  </div>
</div>

https://i.sstatic.net/ZRbOa.png

Answer №1

When using the productService.getAll() method to fetch data from an API, there is a delay in retrieving the data. However, the route.queryParamMap function executes immediately. This causes the this.products object to be empty when it is subscribed to, making it unable to filter properly. Additionally, the this.products = products initialization is missing in the subscription block. To resolve these issues, you can restructure the code as shown below:

    export class ProductsComponent {

     products: Product[] = [];
     filteredProducts: Product[] = [];
    categories$: Observable<any>;
    category: string;
    products$: Observable<any>


     constructor(
        route: ActivatedRoute,
        productService: ProductService, 
        categoryService: CategoryService) {
        this.filteredProducts = [];


        //Displays Categories in View
        this.categories$ = categoryService.getAll();
        route.queryParamMap.subscribe(params => {
          this.category = params.get('category');
            });


    productService.getAll().subscribe(products => {
                   this.products = products;
                             //Set the Filtered Products Array
                   this.filteredProducts = (this.category) ? 
                   this.products.filter(p => {
                      return p.category === this.category;
                   }) : this.products;
                   console.log(this.filteredProducts); // Log the FilteredProducts Array
}); 
       }

     }

The following output is received when logging as follows: productService.getAll().subscribe(products => { console.log(products); this.products = products; });

https://i.sstatic.net/8AXUD.png and displayed in the CMD:

https://i.sstatic.net/XM2G4.png

Answer №2

There are a few mistakes in your code, the first one being that you have

productService.getAll().subscribe (products => this.products);

which should actually be

productService.getAll().subscribe(products => this.products = products);

However, this won't work because it is an asynchronous call.

So, the first step is to update your productService to "cache" the products. Your service should look like this:

products: any[]
getAll() {
    return this.products ? of(product) :
           this.httpClient.get("your-url").pipe(
              tap(res=>this.products=res;
           )
}

We are using of from 'rxjs' and tap from 'rxjs/operators'

This modification ensures that getProducts returns an Observable of products. The first time it is called, "products" is undefined, so it returns the response of httpClient.get. The tap function updates the value of products, so the next time it is called, the variable products will be used.

The second mistake is using the "constructor", instead use ngOnInit to subscribe to route.queryParamMap.

ngOnInit()
{
   this.route.queryParamMap.subscribe(params => {
      this.category = params.get('category');

      //here you get all the products
      this.productService.getAll().subscribe (
         products => this.products=products);
         //Setting the filtered Products Array
         this.filteredProducts = (this.category) ? 
             this.products.filter(p => {
              return p.category === this.category;
         }) : 
         this.products;
         console.log(this.filteredProducts); // Console.log FilteredProducts Array
      });
}

Another approach would be to use switchMap and make filteredProducts an observable (then we can use filteredProduct$ | async)

this.filteredProduct$ = this.route.queryParamMap.pipe(
     switchMap(params=>{
       this.category=params.get('category');
       return this.productService.getAll().pipe( 
          switchMap(products=>{
              const filteredProduct=!this.category?products:
                   products.filter(p => {
                      return p.category === this.category;
                    }) : 
             return of(filteredProduct):

          })
       )
     })
   ) 

NOTE: I haven't checked the code but this is the general idea, I hope this explanation is helpful

Answer №3

The issue lies within the filtering process, specifically when attempting to filter products based on their category. Unfortunately, the filter does not seem to be working as expected due to the fact that the program is unable to retrieve the product's category using p.category. This could be attributed to the use of the latest versions of Angular and Firebase. To resolve this, consider utilizing p.payload.val().category instead, allowing for a successful comparison with this.category.

Feel free to try out the following code snippet:

productService.getAll().pipe(switchMap(products => {
    this.products = products;
    return route.queryParamMap;
  }))
      .subscribe(params => {
      this.category = params.get('category');
      this.filterProducts = (this.category) ?
      this.products.filter(p => (p.payload.val().category)===this.category) :
      this.products;
    });

Answer №4

It's easy to solve this issue! I wasted 2 hours trying to figure it out. When passing queryParams using the c.key, the value is in lowercase like "bread," while our product category value is in uppercase like "Bread." Therefore, you need to provide the value of the category name instead of the category key. Just make this simple change:

Change THIS:

<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.payload.val().name }}
</a>

TO:

<a *ngFor="let c of categories$ | async" 
    routerLink="/"
    [queryParams]="{ category: c.payload.val().name }"
    class="list-group-item list-group-item-action"
    [class.active] = 'category === c.payload.val().name'
    >
    {{ c.payload.val().name }}
</a>

Answer №5

the correct answer for the most recent version of Angular is:

products.component.ts:

import { switchMap } from 'rxjs/operators';
import { Product } from './../models/product';
import { Category } from './../models/category';
import { ActivatedRoute } from '@angular/router';
import { ProductService } from './../product.service';
import { Component, OnInit } from '@angular/core';
import { CategoryService } from '../category.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent {

products: Product[] = [];
filteredProducts: Product[] = [];
categories$: Observable<any>;
category: string;
products$: Observable<any>;

constructor(
route: ActivatedRoute,
productService: ProductService,
categoryService: CategoryService
) {
productService
.getAll().pipe(
switchMap(products => {
this.products = products;
return route.queryParamMap;
}))
.subscribe(params => {
this.category = params.get('category');
this.filteredProducts = (this.category) ?
this.products.filter(p => p.category === this.category) :
this.products;
});
this.categories$ = categoryService.getAll();
}
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tracking the Height of Hidden Elements During Page Resizing

I am currently attempting to determine the height of a hidden element after the page has been resized. More details can be found at this link: The script is able to successfully log the height upon page load, but when the page is resized, it goes back to ...

Oops! There seems to be a problem with your AngularJS/JavaScript code. The error message is "Uncaught ReferenceError:

As I venture into the world of AngularJS and JavaScript, I am working on creating a 'blog' using these technologies. Here is my HTML view code- <div ng-controller="Controller1"> <form ng-submit="sub()" role="form"> Title: <textar ...

Could someone break down for me the behavior exhibited within this method?

Hello there, I'm a beginner so please forgive me for any lack of knowledge. const example = { myFunction(){ console.log(this); }, myFunction2(){ function myFunction3(){ console.log(this) } return ...

Is there a method for configuring NextAuth to utilize the /src/app directory instead?

Is there a specific method to instruct NextAuth to redirect to this particular directory, but within the src/app directory? All of my routes are now located there due to the changes in Next.js 13. Below is an example of the code I am using: export const a ...

I'm having trouble getting this angular expression to cooperate in Plunker. Can anyone shed some light on why {{ 843 / 42

I'm currently diving into Angular with the help of Plural Sight. The initial lesson dives into utilizing the ng-app directive. For those interested, here's a direct link to the Plunker editor: http://plnkr.co/edit/HIDCS8A9CR1jnAIDR0Zb?p=preview ...

Easiest method for combining elements made in Angular with D3

Exploring various methods to combine D3 and Angular for learning purposes, seeking advice on the approach: The client application is fed a JSON array of nodes and edges from the server. The main component is a graph visualization using D3 force-directed l ...

Tips for effectively utilizing innerHTML in this particular scenario

For an assignment, we were tasked with creating a Madlib game where users input words into textfields to replace certain words in a hidden paragraph within the HTML using JavaScript and CSS. The paragraph embedded in the HTML page is as follows: <span ...

Using Material-UI and React: Implementing a DatePicker within a Component instead of a Function

Could someone assist me in getting this code to function properly within React components? import React, { Fragment, useState } from "react"; import DateFnsUtils from "@date-io/date-fns"; // choose your lib import { DatePicker, MuiPickersUtilsProvider } f ...

Refreshing a DataTable by recreating it with a VueJS-populated HTML table

I am retrieving data from a database using Ajax and presenting it in an HTML table with the help of VueJS. Users can add, edit, and delete rows without any issues. However, I face a challenge when trying to incorporate a DataTable to provide users with th ...

The custom verification request page for NextAuth is failing to load after logging in

When using Next Auth with a custom verify request page, some users are experiencing issues where the page hangs or stays on the same page after signing in. The error message displayed is as follows, does anyone know what might be causing this? API resolved ...

Quirks of Emscripten Exported Functions in Visual Studio

As I start delving into Emscripten, I've encountered a puzzling issue with exporting functions for use in JavaScript. Working on a test project involving libsquish, the specific details aren't crucial to my question beyond the header/code filenam ...

Switching Primary Menu Selection When Scrolling Through Various Menu Options

I need help with changing the active class of links on my single page scrolling website. I found some useful code snippets for smooth scrolling and updating the active class on scroll or click events: $(document).ready(function () { $(document).on(" ...

Fade in elements as you scroll using Jquery

I'm currently working on a timeline feature for my website, and I want each element with the class '.event' to fade in as the user scrolls through the timeline. However, I'm running into an issue where all elements fade in simultaneousl ...

Is there a way to design a form that appears as though it's levitating above a div element?

I am new to web development and currently practicing by converting PSD designs into HTML pages. I am facing an issue where I need to create a form that appears to be floating on top of a div, with the form being taller than the div itself. Here is an illu ...

When sorting in AngularJS using the orderBy filter, remember that string values should come before numeric values: for example, sort as follows (n/a, 0,

While running an AngularJS filter sorting on a table, I encountered an issue where if the value is 'n/a' (which is a numeric string), the sorting is incorrect. I expected the order to be n/a, 0, 1, 2, 5, 100 since strings should be considered l ...

The Iron Seal feature is ineffective when a user tries to log in

Iron.seal isn't properly updating the npm module Iron, which is causing login issues for users. var obj = { a: 1, b: 2, c: [3, 4, 5], d: { e: 'f' } }; var password = 'some_not_random_password_that_is_at_lea ...

Error: The filter argument must be in object form

Currently I am in the process of developing a REST API with an endpoint for posting movies. The request body is expected to contain only the movie title, which needs to be validated for presence. Upon receiving the title, I need to fetch other movie detail ...

The controller function is not triggered if the user does not have administrator privileges

I have a code snippet in which my controller function is not being triggered for users who do not have the role of "Admin". Can someone help me identify where I went wrong? Just to clarify, the controller function works fine for users with the role of "Adm ...

The ng-model used within an *ngFor loop is not displaying the correct value

I am working with a JSON file in my Angular2 App. The specific JSON object I am referring to is named tempPromotion and I am trying to access the data within ['results'] like this: tempPromotion['response_payload']['ruleset_list ...

Is it true that the react setState function does not function properly with stream data?

I'm currently utilizing PouchDB to synchronize my remote database with my local database. The following code is integrated within the componentDidMount lifecycle method: const that = this var localDB = new PouchDB('localdb') var remoteDB = ...