All files / src/app/shared/components/product/product-add-to-basket product-add-to-basket.component.ts

94.11% Statements 32/34
78.57% Branches 11/14
88.88% Functions 8/9
93.75% Lines 30/32

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 98 99 100 101 102 103 104 1057x 7x 7x 7x   7x 7x 7x 7x 7x                             7x       9x               9x             9x 9x 9x               9x   9x     9x             18x   9x   8x         9x     9x   9x 9x 9x 9x     9x             39x                 24x      
import { ChangeDetectionStrategy, Component, DestroyRef, Input, OnInit, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
 
import { AccountFacade } from 'ish-core/facades/account.facade';
import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
import { ProductContextFacade } from 'ish-core/facades/product-context.facade';
import { ProductHelper } from 'ish-core/models/product/product.model';
import { whenFalsy } from 'ish-core/utils/operators';
 
/**
 * Displays an add to cart button with an icon or a text label. After clicking the button a loading animation is displayed
 *
 * @example
 * <ish-product-add-to-basket
    [cssClass]="'btn-lg'"
  ></ish-product-add-to-basket>
 */
@Component({
  selector: 'ish-product-add-to-basket',
  templateUrl: './product-add-to-basket.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductAddToBasketComponent implements OnInit {
  /**
   * when 'icon', the button label is an icon, otherwise it is text
   */
  @Input() displayType: 'icon' | 'link' = 'link';
  /**
   * additional css styling
   */
  @Input() cssClass: string;
  /**
   * hidden for screen readers
   */
  @Input() ariaHidden = false;
 
  private basketLoading$: Observable<boolean>;
  visible$: Observable<boolean>;
  translationKey$: Observable<string>;
 
  constructor(
    private checkoutFacade: CheckoutFacade,
    private accountFacade: AccountFacade,
    private context: ProductContextFacade
  ) {}
 
  buttonDisabled$: Observable<boolean>;
 
  /**
   * fires 'true' after add To Cart is clicked and basket is loading
   */
  displaySpinner$ = new BehaviorSubject(false);
 
  private destroyRef = inject(DestroyRef);
 
  ngOnInit() {
    this.visible$ = combineLatest([
      this.context.select('displayProperties', 'addToBasket'),
      // manually connect the product context to product prices since they are required to determine the visibility of the add to basket button
      // this connection is not automatically the case if the product context is not used to display product prices too (e.g. product compare)
      // the prices itself are not directly used here but in the ProductContextDisplayPropertiesService canBeOrderedWithPrice calculation
      // start with undefined to trigger the visibility calculation for cases where there is no product context (e.g. order templates)
      this.context.select('prices').pipe(startWith(undefined)),
    ]).pipe(map(([addToBasketVisible]) => addToBasketVisible));
 
    this.translationKey$ = this.context.select('product').pipe(
      map(product =>
        ProductHelper.isRetailSet(product) ? 'product.add_to_cart.retailset.link' : 'product.add_to_cart.link'
      ),
      startWith('product.add_to_cart.link')
    );
 
    this.basketLoading$ = this.checkoutFacade.basketLoading$;
 
    // update emitted to display spinning animation
    this.basketLoading$.pipe(whenFalsy(), takeUntilDestroyed(this.destroyRef)).subscribe(this.displaySpinner$); // false
 
    const hasQuantityError$ = this.context.select('hasQuantityError');
    const hasProductError$ = this.context.select('hasProductError');
    const hasNoQuantity$ = this.context.select('quantity').pipe(map(quantity => quantity <= 0));
    const loading$ = this.displaySpinner$.pipe(startWith(false));
 
    // disable button when spinning, in case of an error (quick order) or during login or basket loading
    this.buttonDisabled$ = combineLatest([
      loading$,
      hasQuantityError$,
      hasProductError$,
      hasNoQuantity$,
      this.accountFacade.userLoading$,
      this.basketLoading$,
    ]).pipe(map(conditions => conditions.some(c => c)));
  }
 
  addToBasket() {
    this.context.addToBasket();
    this.displaySpinner$.next(true);
  }
 
  get displayIcon(): boolean {
    return this.displayType === 'icon';
  }
}