All files / src/app/shared/components/product/product-listing product-listing.component.ts

91.17% Statements 31/34
68.75% Branches 11/16
78.57% Functions 11/14
90% Lines 27/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 984x 4x 4x 4x 4x   4x 4x   4x                 4x       7x   4x 4x               4x   4x   4x     4x 4x 4x 10x 4x     4x   4x     4x     4x                 4x                                                   8x       4x      
import { ChangeDetectionStrategy, Component, DestroyRef, Input, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, Observable } from 'rxjs';
import { concatMap, map, take, withLatestFrom } from 'rxjs/operators';
 
import { ShoppingFacade } from 'ish-core/facades/shopping.facade';
import { ProductListingID, ProductListingView } from 'ish-core/models/product-listing/product-listing.model';
import { ViewType } from 'ish-core/models/viewtype/viewtype.types';
import { whenFalsy, whenTruthy } from 'ish-core/utils/operators';
 
@Component({
  selector: 'ish-product-listing',
  templateUrl: './product-listing.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  // merged query parameters for product detail links are needed to apply previously selected filter options for variation masters too
  providers: [{ provide: 'PRODUCT_QUERY_PARAMS_HANDLING', useValue: 'merge' }],
})
export class ProductListingComponent implements OnInit {
  @Input() categoryId: string;
  // TODO: make this a proper signal with the next Angular version
  @Input() set productListingId(value: ProductListingID) {
    this.productListingId$.next(value);
  }
  @Input() mode: 'endless-scrolling' | 'paging' = 'endless-scrolling';
  @Input() fragmentOnRouting = 'product-list-top';
 
  productListingView$: Observable<ProductListingView>;
  viewType$: Observable<ViewType>;
  listingLoading$: Observable<boolean>;
  currentPage$: Observable<number>;
  sortBy$: Observable<string>;
 
  private productListingId$ = new BehaviorSubject<ProductListingID>(undefined);
 
  private destroyRef = inject(DestroyRef);
 
  constructor(private shoppingFacade: ShoppingFacade, private router: Router, private activatedRoute: ActivatedRoute) {}
 
  ngOnInit() {
    this.viewType$ = this.shoppingFacade.productListingViewType$;
    this.listingLoading$ = this.shoppingFacade.productListingLoading$;
    this.currentPage$ = this.activatedRoute.queryParamMap.pipe(map(params => +params.get('page') || 1));
    this.sortBy$ = this.activatedRoute.queryParamMap.pipe(map(params => params.get('sorting')));
    this.productListingView$ = this.shoppingFacade.productListingView$(this.productListingId$);
 
    // append view queryParam to URL if none is set
    this.activatedRoute.queryParamMap
      .pipe(
        map(params => params.has('view')),
        take(1),
        whenFalsy(),
        concatMap(() => this.viewType$.pipe(whenTruthy(), take(1))),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe(view => this.changeViewType(view));
  }
 
  /**
   * Emits the event for switching the view type of the product list.
   *
   * @param view The new view type.
   */
  private changeViewType(view: ViewType) {
    this.router.navigate([], {
      relativeTo: this.activatedRoute,
      replaceUrl: true,
      queryParamsHandling: 'merge',
      queryParams: { view },
    });
  }
 
  /**
   * Emits the event for loading more products.
   */
  loadMoreProducts(direction: 'up' | 'down') {
    this.productListingView$
      .pipe(
        take(1),
        map(view => (direction === 'down' ? view.nextPage() : view.previousPage())),
        whenTruthy(),
        withLatestFrom(this.productListingId$),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe(([page, id]) => {
        this.shoppingFacade.loadMoreProducts(id, page);
      });
  }
 
  get isEndlessScrolling() {
    return this.mode === 'endless-scrolling';
  }
 
  get isPaging() {
    return !this.isEndlessScrolling;
  }
}