Home > Software engineering >  How to randomize (shuffle) an angular Observable?
How to randomize (shuffle) an angular Observable?

Time:11-03

I am working with angular and firebase. I have 170 products in firebase. when I call the firebase I get the Observable of the products which stored in products$.

Problem: I would like to shuffle all the products among themselves. So that when I refresh the web page, every time the product list will be different. I tried the array method but it doesn't work as products$ is an Observable not array! What I could do? Thanks in Advance!

product.component.ts
import { Product } from './../models/product';
import { Cart } from './../models/cart';
import { CartService } from './../cart.service';
import { ActivatedRoute } from '@angular/router';
import { ProductsService } from './../products.service';
import { map, switchMap } from 'rxjs/operators';
import { Observable, of, Subscription } from 'rxjs';
import { Component } from '@angular/core';

@Component({
  selector: 'products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css'],
})
export class ProductsComponent {
  products$: Observable<Product[]>;
 
  cart$: Observable<Cart>;

  constructor(
    private productsService: ProductsService,
    private cartService: CartService,
    private route: ActivatedRoute
  ) {
    this.getProducts();
    this.getCart();
  }

  private getProducts(): void {
    this.products$ = this.route.queryParamMap.pipe(
      switchMap((params) => {
        if (!params) return of(null);

        let category = params.get('category');
        return this.applyFilter(category);
      })
    );
  }

  private applyFilter(category: string): Observable<Product[]> {
    if (!category)
      return this.productsService
        .getAll()
        .snapshotChanges()
        .pipe(
          map((sps) => sps.map((sp) => ({ key: sp.key, ...sp.payload.val() })))
        );

    return this.productsService
      .getByCategory(category)
      .snapshotChanges()
      .pipe(
        map((sps) => sps.map((sp) => ({ key: sp.key, ...sp.payload.val() })))
      );
  }

  private async getCart() {
    this.cart$ = (await this.cartService.getCart())
      .snapshotChanges()
      .pipe(
        map(
          (sc) =>
            new Cart(
              sc.key,
              sc.payload.val().cartLines,
              sc.payload.val().createdOn
            )
        )
      );
  }
}
product.component.html
<div >
    <div >
        <products-filter></products-filter>
    </div>
    <div >
        <div *ngIf="cart$ | async as cart" >
            <ng-container *ngFor="let product of products$ | async; let i = index">
                <div >
                    <product-card [product]="product" [cart]="cart"></product-card>
                    <div *ngIf="(i   1) % 2 === 0" ></div>
                </div>
            </ng-container>
        </div>
        <div *ngIf="(products$ | async)?.length === 0"  role="alert">
        <h1>Keine Produkte gefunden!</h1>
        </div>
    </div>
</div>

CodePudding user response:

For sure there is a better/shorter way to do this, but you can try using a "BehaviourSubject" and pass it your array, sorted as you like (randomly in this case).

You can try something like this:

  1. Add this to your product.component.ts:
// product.component.ts

import { ActivatedRoute } from '@angular/router';
import { Component } from '@angular/core';
import { BehaviorSubject, Observable, of, Subscription } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

import { Product } from './../models/product';
import { Cart } from './../models/cart';
import { CartService } from './../cart.service';
import { ProductsService } from './../products.service';

@Component({
  selector: 'products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css'],
})
export class ProductsComponent {
  products$: Observable<Product[]>;
  randomProductsSource = new BehaviorSubject<Product[] | null>(null);
  randomProducts$ = this.randomProductsSource.asObservable();
  cart$: Observable<Cart>;

  constructor(
    private productsService: ProductsService,
    private cartService: CartService,
    private route: ActivatedRoute
  ) {
    this.getProducts();
    this.getCart();

    this.products$.subscribe( _products => {
      if ( _products && products?.length > 0) { 
         this.randomizeArray(_products);
         this.setRandomProducts(_products);
      }
    })

  }

  private getProducts(): void {
    this.products$ = this.route.queryParamMap.pipe(
      switchMap((params) => {
        if (!params) return of(null);

        let category = params.get('category');
        return this.applyFilter(category);
      })
    );
  }

  private applyFilter(category: string): Observable<Product[]> {
    if (!category)
      return this.productsService
        .getAll()
        .snapshotChanges()
        .pipe(
          map((sps) => sps.map((sp) => ({ key: sp.key, ...sp.payload.val() })))
        );

    return this.productsService
      .getByCategory(category)
      .snapshotChanges()
      .pipe(
        map((sps) => sps.map((sp) => ({ key: sp.key, ...sp.payload.val() })))
      );
  }

  private async getCart() {
    this.cart$ = (await this.cartService.getCart())
      .snapshotChanges()
      .pipe(
        map(
          (sc) =>
            new Cart(
              sc.key,
              sc.payload.val().cartLines,
              sc.payload.val().createdOn
            )
        )
      );
  }

  private setRandomProducts(data: Product[] | null) {
    this.randomProductsSource.next(data);
  }

  private randomizeArray(arrayProducts) {
     for (let i = arrayProducts.length-1; i > 0; i--) {
        const j = Math.floor( Math.random()*(i 1) );
        [arrayProducts[i], arrayProducts[j]] = [arrayProducts[j], arrayProducts[i]];
     }
  }


}


  1. Then change in your product.component.html, just change products$ by randomProducts$:
<div *ngIf="(randomProducts$ | async)?.length === 0"  role="alert">

I got the idea of the randomize method from HERE

  • Related