Guest User

Untitled

a guest
Sep 25th, 2025
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { Injectable, inject, effect, computed } from '@angular/core';
  2. import {
  3.   signalStore,
  4.   withState,
  5.   withMethods,
  6.   patchState,
  7.   withHooks,
  8.   withComputed,
  9. } from '@ngrx/signals';
  10. import { rxMethod } from '@ngrx/signals/rxjs-interop';
  11. import { pipe, switchMap, tap, catchError, of } from 'rxjs';
  12. import { Product } from './product.model';
  13. import { ProductService } from './product.service';
  14.  
  15. interface ProductState {
  16.   products: Product[];
  17.   isLoading: boolean;
  18.   error: string | null;
  19. }
  20.  
  21. const initialState: ProductState = {
  22.   products: [],
  23.   isLoading: false,
  24.   error: null,
  25. };
  26.  
  27. @Injectable({
  28.   providedIn: 'root',
  29. })
  30. export class ProductStore extends signalStore(
  31.   withState(initialState),
  32.   withComputed((store) => ({
  33.     lowStockProducts: computed(() =>
  34.       store.products().filter((p) => p.stock < 10)
  35.     ),
  36.   })),
  37.   withMethods((store, productService = inject(ProductService)) => ({
  38.     loadProducts: rxMethod<void>(
  39.       pipe(
  40.         tap(() => patchState(store, { isLoading: true, error: null })),
  41.         switchMap(() => productService.getAll()),
  42.         tap((res) =>
  43.           patchState(store, {
  44.             products: res.products,
  45.             isLoading: false,
  46.             error: null,
  47.           })
  48.         ),
  49.         catchError((error) => {
  50.           patchState(store, {
  51.             products: [],
  52.             isLoading: false,
  53.             error:
  54.               error instanceof Error
  55.                 ? error.message
  56.                 : 'Failed to load products',
  57.           });
  58.           return of(null);
  59.         })
  60.       )
  61.     ),
  62.  
  63.     addProduct(newProduct: Product): void {
  64.       const currentProducts = store.products();
  65.       const exists = currentProducts.some((p) => p.id === newProduct.id);
  66.       if (exists) {
  67.         patchState(store, {
  68.           error: `Product with ID ${newProduct.id} already exists`,
  69.         });
  70.         return;
  71.       }
  72.       patchState(store, {
  73.         products: [...currentProducts, newProduct],
  74.         error: null,
  75.       });
  76.     },
  77.  
  78.     addProductToApi: rxMethod<Product>(
  79.       pipe(
  80.         tap(() => patchState(store, { isLoading: true, error: null })),
  81.         switchMap((newProduct) => {
  82.           // Check if product already exists locally first
  83.           const currentProducts = store.products();
  84.           const exists = currentProducts.some(
  85.             (p) => p.title === newProduct.title
  86.           );
  87.           if (exists) {
  88.             throw new Error(
  89.               `Product with title "${newProduct.title}" already exists`
  90.             );
  91.           }
  92.           return productService.create(newProduct);
  93.         }),
  94.         tap((createdProduct) => {
  95.           const currentProducts = store.products();
  96.           patchState(store, {
  97.             products: [...currentProducts, createdProduct],
  98.             isLoading: false,
  99.             error: null,
  100.           });
  101.         }),
  102.         catchError((error) => {
  103.           patchState(store, {
  104.             isLoading: false,
  105.             error:
  106.               error instanceof Error ? error.message : 'Failed to add product',
  107.           });
  108.           return of(null);
  109.         })
  110.       )
  111.     ),
  112.  
  113.     updateProductToApi: rxMethod<{
  114.       productId: number;
  115.       updatedProduct: Partial<Product>;
  116.     }>(
  117.       pipe(
  118.         tap(() => patchState(store, { isLoading: true, error: null })),
  119.         switchMap(({ productId, updatedProduct }) => {
  120.           // Check if product exists locally first
  121.           const currentProducts = store.products();
  122.           const productIndex = currentProducts.findIndex(
  123.             (p) => p.id === productId
  124.           );
  125.           if (productIndex === -1) {
  126.             throw new Error(`Product with ID ${productId} not found`);
  127.           }
  128.           return productService.update(productId, updatedProduct);
  129.         }),
  130.         tap((updatedProduct) => {
  131.           const currentProducts = store.products();
  132.           const productIndex = currentProducts.findIndex(
  133.             (p) => p.id === updatedProduct.id
  134.           );
  135.           const updatedProducts = [...currentProducts];
  136.           updatedProducts[productIndex] = updatedProduct;
  137.           patchState(store, {
  138.             products: updatedProducts,
  139.             isLoading: false,
  140.             error: null,
  141.           });
  142.         }),
  143.         catchError((error) => {
  144.           patchState(store, {
  145.             isLoading: false,
  146.             error:
  147.               error instanceof Error
  148.                 ? error.message
  149.                 : 'Failed to update product',
  150.           });
  151.           return of(null);
  152.         })
  153.       )
  154.     ),
  155.  
  156.     deleteProductToApi: rxMethod<number>(
  157.       pipe(
  158.         tap(() => patchState(store, { isLoading: true, error: null })),
  159.         switchMap((productId) => {
  160.           // Check if product exists locally first
  161.           const currentProducts = store.products();
  162.           const exists = currentProducts.some((p) => p.id === productId);
  163.           if (!exists) {
  164.             throw new Error(`Product with ID ${productId} not found`);
  165.           }
  166.           return productService.delete(productId).pipe(
  167.             tap(() => productId), // Pass productId to next operator
  168.             switchMap(() => of(productId)) // Return productId for the final tap
  169.           );
  170.         }),
  171.         tap((productId) => {
  172.           const currentProducts = store.products();
  173.           patchState(store, {
  174.             products: currentProducts.filter((p) => p.id !== productId),
  175.             isLoading: false,
  176.             error: null,
  177.           });
  178.         }),
  179.         catchError((error) => {
  180.           patchState(store, {
  181.             isLoading: false,
  182.             error:
  183.               error instanceof Error
  184.                 ? error.message
  185.                 : 'Failed to delete product',
  186.           });
  187.           return of(null);
  188.         })
  189.       )
  190.     ),
  191.  
  192.     updateProduct(productId: number, updatedProduct: Product): void {
  193.       const currentProducts = store.products();
  194.       const productIndex = currentProducts.findIndex((p) => p.id === productId);
  195.       if (productIndex === -1) {
  196.         patchState(store, {
  197.           error: `Product with ID ${productId} not found`,
  198.         });
  199.         return;
  200.       }
  201.       const updatedProducts = [...currentProducts];
  202.       updatedProducts[productIndex] = updatedProduct;
  203.       patchState(store, {
  204.         products: updatedProducts,
  205.         error: null,
  206.       });
  207.     },
  208.  
  209.     deleteProduct(productId: number): void {
  210.       const currentProducts = store.products();
  211.       const exists = currentProducts.some((p) => p.id === productId);
  212.       if (!exists) {
  213.         patchState(store, {
  214.           error: `Product with ID ${productId} not found`,
  215.         });
  216.         return;
  217.       }
  218.       patchState(store, {
  219.         products: currentProducts.filter((p) => p.id !== productId),
  220.         error: null,
  221.       });
  222.     },
  223.  
  224.     findProduct(productId: number): Product | undefined {
  225.       return store.products().find((p) => p.id === productId);
  226.     },
  227.  
  228.     productExists(productId: number): boolean {
  229.       return store.products().some((p) => p.id === productId);
  230.     },
  231.  
  232.     clearError(): void {
  233.       patchState(store, { error: null });
  234.     },
  235.  
  236.     reset(): void {
  237.       patchState(store, initialState);
  238.     },
  239.   })),
  240.   // ✅ AUTOMATIC SORTING using withHooks and effect
  241.   withHooks({
  242.     onInit(store) {
  243.       effect(() => {
  244.         const products = store.products();
  245.         // Only sort if products exist and we have more than one product
  246.         if (products && products.length > 1) {
  247.           // Check if the array is already sorted to avoid unnecessary updates
  248.           const sorted = [...products].sort((a, b) =>
  249.             a.title.localeCompare(b.title)
  250.           );
  251.           // Only update if the order actually changed
  252.           const needsSorting = products.some(
  253.             (prod, index) => prod.id !== sorted[index]?.id
  254.           );
  255.           if (needsSorting) {
  256.             console.log('Products not sorted, auto-sorting now...');
  257.             patchState(store, { products: sorted });
  258.           }
  259.         }
  260.       });
  261.     },
  262.   })
  263. ) {}
  264.  
Advertisement
Add Comment
Please, Sign In to add comment