Angular @for acknowledge changes in list

35 Views Asked by At

I am writing a e-commerce application using Angular 17.

I have a list of products that I iterate through using the @for block from angular/core in one of my HTML component files.

Here is an oversimplified version of my code:

@for(product of products; track product){
  <h3>{{product.name}}</h3>
  <span>{{product.price}}</span>
}

My issue is that the products list might change because of user input (filtering through products).

How can I change the HTML for my @for in order to be mindful of list changes and sync automatically without the need to refresh my page?

So far I have only found information about using ngFor, but I really want to use the new @for block.

1

There are 1 best solutions below

0
Naren Murali On

We can use fromEvent and a method where we store the original value and a rxjs stream, where we listen for the event input using fromEvent and then we use map to modify the values using filter array method, finally we return the values. fromEvent does not get fired initially, so we use merge and of to fire the initial value which is the entire array!

Best resource for learning about RXJS operators used here!

import { Component, ElementRef, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { fromEvent, merge, Observable, of } from 'rxjs';
import {
  map,
  debounceTime,
} from 'rxjs/operators';
import 'zone.js';
export interface Product {
  name: string;
  price: number;
}
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule],
  template: `
    <input type="text" #search/>
    @for(product of products | async; track product){
  <h3>{{product.name}}</h3>
  <span>{{product.price}}</span>
}
  `,
})
export class App {
  @ViewChild('search') search!: ElementRef<HTMLInputElement>;
  name = 'Angular';
  searchStr = '';
  products!: Observable<Product[]>;
  productsOriginal: Product[] = [
    { name: 'test1', price: 12 },
    { name: 'testxcvbxcv2', price: 13 },
    { name: 'xcvb', price: 14 },
    { name: 'wert', price: 15 },
    { name: 'sadfasdf', price: 161 },
    { name: 'asdfasdf', price: 18 },
    { name: 'asdf', price: 119 },
  ];

  ngAfterViewInit() {
    // if you are going to change the values of product inside the ngFor
    // we need to clone productsOriginal, since updating products might modify
    // productsOriginal
    const products = structuredClone(this.productsOriginal);
    this.products = merge(
      // set the initial value of the products array
      of(products),
      // subscribe to key strokes on search
      fromEvent(this.search.nativeElement, 'input').pipe(
        debounceTime(500), // prevent event from firing multiple times, take only event with 500ms gap
        map((event: any) => {
          // get the input search value
          const searchKey = event?.target?.value;
          // filter the array if it includes the value and return it!
          return this.productsOriginal.filter((product: Product) =>
            product.name.includes(searchKey)
          );
        })
      )
    );
  }
}

bootstrapApplication(App);

Stackblitz Demo