Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced Signal Store: Structuring State for Re...

Advanced Signal Store: Structuring State for Real Angular Applications

Modern Angular applications grow quickly, and with that growth come new questions about how to structure and share state effectively. Signals make local state simple and expressive, but as applications evolve, architectural decisions around state become increasingly important.

In this talk, we go beyond the basics of the NgRx Signal Store and explore how it can be used as an architectural building block in real Angular applications. We look at practical scenarios and discuss where state belongs, how to define clear boundaries between components, features, and application wide concerns, and how Signal Store fits into that picture.

We dive deeper into advanced Signal Store concepts such as store composition, derived state, effects, and integration with Angular's dependency injection system, always from an architectural point of view rather than focusing only on the API.

This session is driven by experience and clear opinions. It focuses on sharing patterns, heuristics, and mental models that help teams structure state with confidence. You will leave with concrete ideas for designing maintainable and scalable Angular applications using Signal Store, grounded in real world constraints and everyday development.

Avatar for Fabian Gosebrink

Fabian Gosebrink

May 13, 2026

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. export class CheckoutService { readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http

    = inject(HttpClient); readonly #cartProducts = signal<Product[]>([]); readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); addToCart(product: Product) { // ... } getCartProducts() { // ... } removeFromCart(index: number) { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  2. export class CheckoutService { readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http

    = inject(HttpClient); readonly #cartProducts = signal<Product[]>([]); readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); addToCart(product: Product) { // ... } getCartProducts() { // ... } removeFromCart(index: number) { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http = inject(HttpClient); export class CheckoutService { 1 2 3 readonly #cartProducts = signal<Product[]>([]); 4 5 readonly #cartProductsChanged = new Subject<Product[]>(); 6 cartProductsChanged = this.#cartProductsChanged.asObservable(); 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20
  3. export class CheckoutService { readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http

    = inject(HttpClient); readonly #cartProducts = signal<Product[]>([]); readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); addToCart(product: Product) { // ... } getCartProducts() { // ... } removeFromCart(index: number) { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http = inject(HttpClient); export class CheckoutService { 1 2 3 readonly #cartProducts = signal<Product[]>([]); 4 5 readonly #cartProductsChanged = new Subject<Product[]>(); 6 cartProductsChanged = this.#cartProductsChanged.asObservable(); 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20 readonly #cartProducts = signal<Product[]>([]); export class CheckoutService { 1 readonly #baseUrl = 'http://localhost:3000/cart/'; 2 readonly #http = inject(HttpClient); 3 4 5 readonly #cartProductsChanged = new Subject<Product[]>(); 6 cartProductsChanged = this.#cartProductsChanged.asObservable(); 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20
  4. export class CheckoutService { readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http

    = inject(HttpClient); readonly #cartProducts = signal<Product[]>([]); readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); addToCart(product: Product) { // ... } getCartProducts() { // ... } removeFromCart(index: number) { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 readonly #baseUrl = 'http://localhost:3000/cart/'; readonly #http = inject(HttpClient); export class CheckoutService { 1 2 3 readonly #cartProducts = signal<Product[]>([]); 4 5 readonly #cartProductsChanged = new Subject<Product[]>(); 6 cartProductsChanged = this.#cartProductsChanged.asObservable(); 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20 readonly #cartProducts = signal<Product[]>([]); export class CheckoutService { 1 readonly #baseUrl = 'http://localhost:3000/cart/'; 2 readonly #http = inject(HttpClient); 3 4 5 readonly #cartProductsChanged = new Subject<Product[]>(); 6 cartProductsChanged = this.#cartProductsChanged.asObservable(); 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20 readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); export class CheckoutService { 1 readonly #baseUrl = 'http://localhost:3000/cart/'; 2 readonly #http = inject(HttpClient); 3 readonly #cartProducts = signal<Product[]>([]); 4 5 6 7 8 addToCart(product: Product) { 9 // ... 10 } 11 12 getCartProducts() { 13 // ... 14 } 15 16 removeFromCart(index: number) { 17 // ... 18 } 19 } 20
  5. @Component({ /* ... */ }) export class CheckoutComponent implements OnInit

    { cartProducts = signal<Product[]>([]); totalAmount = signal(0); private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } onRemoveClicked(index: number): void { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  6. @Component({ /* ... */ }) export class CheckoutComponent implements OnInit

    { cartProducts = signal<Product[]>([]); totalAmount = signal(0); private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } onRemoveClicked(index: number): void { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 3 4 private readonly toastrService = inject(ToastrService); 5 private readonly checkoutService = inject(CheckoutService); 6 7 ngOnInit(): void { 8 merge( 9 this.checkoutService.getCartProducts(), 10 this.checkoutService.cartProductsChanged, 11 ).subscribe((products) => { 12 this.cartProducts.set(products); 13 const totalAmount = this.calculateTotalAmount(products); 14 this.totalAmount.set(totalAmount); 15 }); 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22
  7. @Component({ /* ... */ }) export class CheckoutComponent implements OnInit

    { cartProducts = signal<Product[]>([]); totalAmount = signal(0); private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } onRemoveClicked(index: number): void { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 3 4 private readonly toastrService = inject(ToastrService); 5 private readonly checkoutService = inject(CheckoutService); 6 7 ngOnInit(): void { 8 merge( 9 this.checkoutService.getCartProducts(), 10 this.checkoutService.cartProductsChanged, 11 ).subscribe((products) => { 12 this.cartProducts.set(products); 13 const totalAmount = this.calculateTotalAmount(products); 14 this.totalAmount.set(totalAmount); 15 }); 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22 private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 cartProducts = signal<Product[]>([]); 3 totalAmount = signal(0); 4 5 6 7 ngOnInit(): void { 8 merge( 9 this.checkoutService.getCartProducts(), 10 this.checkoutService.cartProductsChanged, 11 ).subscribe((products) => { 12 this.cartProducts.set(products); 13 const totalAmount = this.calculateTotalAmount(products); 14 this.totalAmount.set(totalAmount); 15 }); 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22
  8. @Component({ /* ... */ }) export class CheckoutComponent implements OnInit

    { cartProducts = signal<Product[]>([]); totalAmount = signal(0); private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } onRemoveClicked(index: number): void { // ... } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 3 4 private readonly toastrService = inject(ToastrService); 5 private readonly checkoutService = inject(CheckoutService); 6 7 ngOnInit(): void { 8 merge( 9 this.checkoutService.getCartProducts(), 10 this.checkoutService.cartProductsChanged, 11 ).subscribe((products) => { 12 this.cartProducts.set(products); 13 const totalAmount = this.calculateTotalAmount(products); 14 this.totalAmount.set(totalAmount); 15 }); 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22 private readonly toastrService = inject(ToastrService); private readonly checkoutService = inject(CheckoutService); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 cartProducts = signal<Product[]>([]); 3 totalAmount = signal(0); 4 5 6 7 ngOnInit(): void { 8 merge( 9 this.checkoutService.getCartProducts(), 10 this.checkoutService.cartProductsChanged, 11 ).subscribe((products) => { 12 this.cartProducts.set(products); 13 const totalAmount = this.calculateTotalAmount(products); 14 this.totalAmount.set(totalAmount); 15 }); 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22 merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 cartProducts = signal<Product[]>([]); 3 totalAmount = signal(0); 4 private readonly toastrService = inject(ToastrService); 5 private readonly checkoutService = inject(CheckoutService); 6 7 ngOnInit(): void { 8 9 10 11 12 13 14 15 16 } 17 18 onRemoveClicked(index: number): void { 19 // ... 20 } 21 } 22
  9. export class ShellComponent implements OnInit { cartProductsCount = signal(0); private

    readonly checkoutService = inject(CheckoutService); ngOnInit() { this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  10. export class ShellComponent implements OnInit { cartProductsCount = signal(0); private

    readonly checkoutService = inject(CheckoutService); ngOnInit() { this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 cartProductsCount = signal(0); private readonly checkoutService = inject(CheckoutService); export class ShellComponent implements OnInit { 1 2 3 4 ngOnInit() { 5 this.checkoutService.getCartProducts().subscribe((products) => { 6 this.cartProductsCount.set(products.length); 7 }); 8 9 this.checkoutService.cartProductsChanged.subscribe((products) => { 10 this.cartProductsCount.set(products.length); 11 }); 12 } 13 } 14
  11. export class ShellComponent implements OnInit { cartProductsCount = signal(0); private

    readonly checkoutService = inject(CheckoutService); ngOnInit() { this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 cartProductsCount = signal(0); private readonly checkoutService = inject(CheckoutService); export class ShellComponent implements OnInit { 1 2 3 4 ngOnInit() { 5 this.checkoutService.getCartProducts().subscribe((products) => { 6 this.cartProductsCount.set(products.length); 7 }); 8 9 this.checkoutService.cartProductsChanged.subscribe((products) => { 10 this.cartProductsCount.set(products.length); 11 }); 12 } 13 } 14 this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); export class ShellComponent implements OnInit { 1 cartProductsCount = signal(0); 2 private readonly checkoutService = inject(CheckoutService); 3 4 ngOnInit() { 5 6 7 8 9 10 11 12 } 13 } 14
  12. export class ProductsComponent implements OnInit { products = signal<{ category:

    string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  13. export class ProductsComponent implements OnInit { products = signal<{ category:

    string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); export class ProductsComponent implements OnInit { 1 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22
  14. export class ProductsComponent implements OnInit { products = signal<{ category:

    string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); export class ProductsComponent implements OnInit { 1 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 products = signal<{ category: string; products: Product[] }[]>([]); 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22
  15. export class ProductsComponent implements OnInit { products = signal<{ category:

    string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); export class ProductsComponent implements OnInit { 1 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 products = signal<{ category: string; products: Product[] }[]>([]); 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} ); private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const groupedByCategory = Object.keys(categoryProductMap).map( 25 (category) => ({ 26 category: CATEGORY NAME MAP[category as ProductCategory], 27
  16. export class ProductsComponent implements OnInit { products = signal<{ category:

    string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 products = signal<{ category: string; products: Product[] }[]>([]); export class ProductsComponent implements OnInit { 1 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); export class ProductsComponent implements OnInit { 1 products = signal<{ category: string; products: Product[] }[]>([]); 2 3 4 5 6 7 ngOnInit() { 8 this.productsService.loadProducts().subscribe((products) => { 9 const categoryProductMap = products.reduce( 10 (result: Record<string, Product[]>, product) => { 11 const category = product.category; 12 13 if (!result[category]) { 14 result[category] = []; 15 } 16 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); return result; }, {} export class ProductsComponent implements OnInit { 1 products = signal<{ category: string; products: Product[] }[]>([]); 2 private readonly productsService = inject(ProductsService); 3 private readonly checkoutService = inject(CheckoutService); 4 private readonly toastrService = inject(ToastrService); 5 private readonly router = inject(Router); 6 7 ngOnInit() { 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const groupedByCategory = Object.keys(categoryProductMap).map( (category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: categoryProductMap[category] }) ); this.products.set(groupedByCategory); 17 result[category].push(product); 18 19 return result; 20 }, 21 {} 22 ); 23 24 25 26 27 28 29 30 31 32 }); 33 } 34 35 onProductClicked(id: string): void { 36 this.router.navigate(['products', id]); 37 } 38 39
  17. APP

  18. src/ └── app/ ├── features/ │ └── ... └── shared/

    ├── services/ └── store/ 1 2 3 4 5 6 7
  19. src/ └── app/ ├── features/ │ └── ... └── shared/

    ├── services/ └── store/ 1 2 3 4 5 6 7 └── app/ └── shared/ └── store/ src/ 1 2 ├── features/ 3 │ └── ... 4 5 ├── services/ 6 7
  20. features └── checkout ├── ... └── store └── checkout-feature.store.ts 1

    2 3 4 5 └── store └── checkout-feature.store.ts features 1 └── checkout 2 ├── ... 3 4 5
  21. │ ├── checkout.html/ts/... │ └── checkout.store.ts features 1 └── checkout

    2 ├── container 3 4 5 ├── ... 6 └── store 7 └── checkout-feature.store.ts 8
  22. Count of Checkout Products Single Product Products Shell Checkout Product

    Details Shared All Products Total Amount Checkout Products
  23. export const GlobalCheckoutStore = signalStore( { providedIn: 'root' }, withState({

    products: [] as Product[] }), withMethods( ( store, toastrService = inject(ToastrService), checkoutService = inject(CheckoutService) ) => ({ addToCart(product: Product) { ... }, removeFromCart(index: number) { ... } }) ) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  24. export const GlobalCheckoutStore = signalStore( { providedIn: 'root' }, withState({

    products: [] as Product[] }), withMethods( ( store, toastrService = inject(ToastrService), checkoutService = inject(CheckoutService) ) => ({ addToCart(product: Product) { ... }, removeFromCart(index: number) { ... } }) ) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 toastrService = inject(ToastrService), checkoutService = inject(CheckoutService) export const GlobalCheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ products: [] as Product[] }), 3 withMethods( 4 ( 5 store, 6 7 8 ) => ({ 9 addToCart(product: Product) { ... }, 10 removeFromCart(index: number) { ... } 11 }) 12 ) 13 ); 14
  25. export const ShellStore = signalStore( withComputed(( _, globalCheckoutStore = inject(GlobalCheckoutStore))

    => ({ cartProductsCount: computed(() => globalCheckoutStore.products().length) })) ); 1 2 3 4 5 6 7 8
  26. @Component({ template: ` <app-header [cartProductsCount]="cartProductsCount()" /> ... `, }) export

    class ShellComponent implements OnInit { cartProductsCount = signal(0); private readonly checkoutService = inject(CheckoutService); ngOnInit() { this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  27. @Component({ template: ` <app-header [cartProductsCount]="store.cartProductsCount()" /> ... `, styleUrl: './shell.component.scss',

    providers: [ShellStore] }) export class ShellComponent { store = inject(ShellStore); } 1 2 3 4 5 6 7 8 9 10 11
  28. @Component({ template: ` <app-header [cartProductsCount]="store.cartProductsCount()" /> ... `, styleUrl: './shell.component.scss',

    providers: [ShellStore] }) export class ShellComponent { store = inject(ShellStore); } 1 2 3 4 5 6 7 8 9 10 11 providers: [ShellStore] @Component({ 1 template: ` 2 <app-header [cartProductsCount]="store.cartProductsCount()" /> 3 ... 4 `, 5 styleUrl: './shell.component.scss', 6 7 }) 8 export class ShellComponent { 9 store = inject(ShellStore); 10 } 11
  29. @Component({ template: ` <app-header [cartProductsCount]="store.cartProductsCount()" /> ... `, styleUrl: './shell.component.scss',

    providers: [ShellStore] }) export class ShellComponent { store = inject(ShellStore); } 1 2 3 4 5 6 7 8 9 10 11 providers: [ShellStore] @Component({ 1 template: ` 2 <app-header [cartProductsCount]="store.cartProductsCount()" /> 3 ... 4 `, 5 styleUrl: './shell.component.scss', 6 7 }) 8 export class ShellComponent { 9 store = inject(ShellStore); 10 } 11 <app-header [cartProductsCount]="store.cartProductsCount()" /> store = inject(ShellStore); @Component({ 1 template: ` 2 3 ... 4 `, 5 styleUrl: './shell.component.scss', 6 providers: [ShellStore] 7 }) 8 export class ShellComponent { 9 10 } 11
  30. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  31. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 withState({ products: [] as Product[] }), export const ProductsStore = signalStore( 1 2 withComputed((store) => ({ 3 productsByCategories: computed(() => { 4 const products = store.products(); 5 const productsByCategory = ...; 6 7 const categories = Object.keys(productsByCategory); 8 9 return categories.map((category) => ({ 10 category: CATEGORY_NAME_MAP[category as ProductCategory], 11 products: productsByCategory[category] 12 })); 13 }) 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22
  32. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 withState({ products: [] as Product[] }), export const ProductsStore = signalStore( 1 2 withComputed((store) => ({ 3 productsByCategories: computed(() => { 4 const products = store.products(); 5 const productsByCategory = ...; 6 7 const categories = Object.keys(productsByCategory); 8 9 return categories.map((category) => ({ 10 category: CATEGORY_NAME_MAP[category as ProductCategory], 11 products: productsByCategory[category] 12 })); 13 }) 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22 productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) export const ProductsStore = signalStore( 1 withState({ products: [] as Product[] }), 2 withComputed((store) => ({ 3 4 5 6 7 8 9 10 11 12 13 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22
  33. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 withState({ products: [] as Product[] }), export const ProductsStore = signalStore( 1 2 withComputed((store) => ({ 3 productsByCategories: computed(() => { 4 const products = store.products(); 5 const productsByCategory = ...; 6 7 const categories = Object.keys(productsByCategory); 8 9 return categories.map((category) => ({ 10 category: CATEGORY_NAME_MAP[category as ProductCategory], 11 products: productsByCategory[category] 12 })); 13 }) 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22 productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) export const ProductsStore = signalStore( 1 withState({ products: [] as Product[] }), 2 withComputed((store) => ({ 3 4 5 6 7 8 9 10 11 12 13 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22 export const ProductsStore = signalStore( withState({ products: [] as Product[] }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  34. @Component({ // ... }) export class ProductsComponent implements OnInit {

    products = signal<{ category: string; products: Product[] }[]>([]); private readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); private readonly toastrService = inject(ToastrService); private readonly router = inject(Router); ngOnInit() { this.productsService.loadProducts().subscribe((products) => { const categoryProductMap = products.reduce( (result: Record<string, Product[]>, product) => { const category = product.category; if (!result[category]) { result[category] = []; } result[category].push(product); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  35. Count of Checkout Products Single Product Products Shell Checkout Product

    Details Shared All Products Total Amount of Checkout Products Checkout Products
  36. Single Product Products Checkout Products Shell Checkout Product Details Shared

    All Products All Products Count of Checkout Products Total Amount of Checkout Products
  37. app/ ├── features/ │ └── ... └── shared/ ├── services/

    └── store/ ├── global-checkout.store.ts └── global-products.store.ts 1 2 3 4 5 6 7 8
  38. app/ ├── features/ │ └── ... └── shared/ ├── services/

    └── store/ ├── global-checkout.store.ts └── global-products.store.ts 1 2 3 4 5 6 7 8 ├── features/ ├── services/ ├── global-checkout.store.ts └── global-products.store.ts app/ 1 2 │ └── ... 3 └── shared/ 4 5 └── store/ 6 7 8
  39. export const GlobalProductsStore = signalStore( { providedIn: 'root' }, withState({

    products: [] as Product[] }), withMethods( (store, productsService = inject(ProductsService)) => ({ getAll: rxMethod<void>( // Get all products from backend ), add(product: Product) { patchState(store, { products: [...store.products(), product] }); }, addMany(products: Product[]) { patchState(store, { products }); } }) ) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  40. export const GlobalProductsStore = signalStore( { providedIn: 'root' }, withState({

    products: [] as Product[] }), withMethods( (store, productsService = inject(ProductsService)) => ({ getAll: rxMethod<void>( // Get all products from backend ), add(product: Product) { patchState(store, { products: [...store.products(), product] }); }, addMany(products: Product[]) { patchState(store, { products }); } }) ) ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 withState({ products: [] as Product[] }), export const GlobalProductsStore = signalStore( 1 { providedIn: 'root' }, 2 3 withMethods( 4 (store, productsService = inject(ProductsService)) => ({ 5 getAll: rxMethod<void>( 6 // Get all products from backend 7 ), 8 add(product: Product) { 9 patchState(store, { products: [...store.products(), product] }); 10 }, 11 addMany(products: Product[]) { 12 patchState(store, { products }); 13 } 14 }) 15 ) 16 ); 17
  41. export const GlobalCheckoutStore = signalStore( { providedIn: 'root' }, withState({

    productIds: [] as string[] }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ products: computed(() => { // Match lokal IDs with global Products/IDs }) })), withMethods( ( store, toastrService = inject(ToastrService), checkoutService = inject(CheckoutService), globalProductsStore = inject(GlobalProductsStore) ) => ({ addToCart(product: Product) { ... }, removeFromCart(index: number) { ... }, loadCheckoutProductsIfNotLoaded: rxMethod<void>( pipe( filter(() => !globalProductsStore.products().length), exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  42. export const GlobalCheckoutStore = signalStore( { providedIn: 'root' }, withState({

    productIds: [] as string[] }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ products: computed(() => { // Match lokal IDs with global Products/IDs }) })), withMethods( ( store, toastrService = inject(ToastrService), checkoutService = inject(CheckoutService), globalProductsStore = inject(GlobalProductsStore) ) => ({ addToCart(product: Product) { ... }, removeFromCart(index: number) { ... }, loadCheckoutProductsIfNotLoaded: rxMethod<void>( pipe( filter(() => !globalProductsStore.products().length), exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { providedIn: 'root' }, withState({ productIds: [] as string[] }), export const GlobalCheckoutStore = signalStore( 1 2 3 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 4 products: computed(() => { 5 // Match lokal IDs with global Products/IDs 6 }) 7 })), 8 withMethods( 9 ( 10 store, 11 toastrService = inject(ToastrService), 12 checkoutService = inject(CheckoutService), 13 globalProductsStore = inject(GlobalProductsStore) 14 ) => ({ 15 addToCart(product: Product) { ... }, 16 removeFromCart(index: number) { ... }, 17 loadCheckoutProductsIfNotLoaded: rxMethod<void>( 18 pipe( 19 filter(() => !globalProductsStore.products().length), 20 exhaustMap(() => 21 checkoutService.getCartProducts().pipe( 22 tapResponse({ 23 next: (products) => { 24
  43. export const GlobalCheckoutStore = signalStore( { providedIn: 'root' }, withState({

    productIds: [] as string[] }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ products: computed(() => { // Match lokal IDs with global Products/IDs }) })), withMethods( ( store, toastrService = inject(ToastrService), checkoutService = inject(CheckoutService), globalProductsStore = inject(GlobalProductsStore) ) => ({ addToCart(product: Product) { ... }, removeFromCart(index: number) { ... }, loadCheckoutProductsIfNotLoaded: rxMethod<void>( pipe( filter(() => !globalProductsStore.products().length), exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { providedIn: 'root' }, withState({ productIds: [] as string[] }), export const GlobalCheckoutStore = signalStore( 1 2 3 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 4 products: computed(() => { 5 // Match lokal IDs with global Products/IDs 6 }) 7 })), 8 withMethods( 9 ( 10 store, 11 toastrService = inject(ToastrService), 12 checkoutService = inject(CheckoutService), 13 globalProductsStore = inject(GlobalProductsStore) 14 ) => ({ 15 addToCart(product: Product) { ... }, 16 removeFromCart(index: number) { ... }, 17 loadCheckoutProductsIfNotLoaded: rxMethod<void>( 18 pipe( 19 filter(() => !globalProductsStore.products().length), 20 exhaustMap(() => 21 checkoutService.getCartProducts().pipe( 22 tapResponse({ 23 next: (products) => { 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ export const GlobalCheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState({ productIds: [] as string[] }), 3 4 products: computed(() => { 5 // Match lokal IDs with global Products/IDs 6 }) 7 })), 8 withMethods( 9 ( 10 store, 11 toastrService = inject(ToastrService), 12 checkoutService = inject(CheckoutService), 13 globalProductsStore = inject(GlobalProductsStore) 14 ) => ({ 15 addToCart(product: Product) { ... }, 16 removeFromCart(index: number) { ... }, 17 loadCheckoutProductsIfNotLoaded: rxMethod<void>( 18 pipe( 19 filter(() => !globalProductsStore.products().length), 20 exhaustMap(() => 21 checkoutService.getCartProducts().pipe( 22 tapResponse({ 23 next: (products) => { 24
  44. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  45. export const ProductsStore = signalStore( withState({ products: [] as Product[]

    }), withComputed((store) => ({ productsByCategories: computed(() => { const products = store.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return categories.map((category) => ({ category: CATEGORY_NAME_MAP[category as ProductCategory], products: productsByCategory[category] })); }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ getAll() // ..., 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 withState({ products: [] as Product[] }), export const ProductsStore = signalStore( 1 2 withComputed((store) => ({ 3 productsByCategories: computed(() => { 4 const products = store.products(); 5 const productsByCategory = ...; 6 7 const categories = Object.keys(productsByCategory); 8 9 return categories.map((category) => ({ 10 category: CATEGORY_NAME_MAP[category as ProductCategory], 11 products: productsByCategory[category] 12 })); 13 }) 14 })), 15 withMethods( 16 ( 17 store, 18 globalCheckoutStore = inject(GlobalCheckoutStore), 19 router = inject(Router) 20 ) => ({ 21 getAll() // ..., 22
  46. export const ProductsStore = signalStore( withComputed((store, globalProductsStore = inject(GlobalProductsStore)) =>

    ({ productsByCategories: computed(() => { const products = globalProductsStore.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return // ...; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ addToCart: globalCheckoutStore.addToCart, onProductClicked(id: string): void { router.navigate(['products', id]); } }) ), withHooks({ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  47. export const ProductsStore = signalStore( withComputed((store, globalProductsStore = inject(GlobalProductsStore)) =>

    ({ productsByCategories: computed(() => { const products = globalProductsStore.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return // ...; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ addToCart: globalCheckoutStore.addToCart, onProductClicked(id: string): void { router.navigate(['products', id]); } }) ), withHooks({ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ export const ProductsStore = signalStore( 1 2 productsByCategories: computed(() => { 3 const products = globalProductsStore.products(); 4 const productsByCategory = ...; 5 6 const categories = Object.keys(productsByCategory); 7 8 return // ...; 9 }) 10 })), 11 withMethods( 12 ( 13 store, 14 globalCheckoutStore = inject(GlobalCheckoutStore), 15 router = inject(Router) 16 ) => ({ 17 addToCart: globalCheckoutStore.addToCart, 18 onProductClicked(id: string): void { 19 router.navigate(['products', id]); 20 } 21 }) 22 ), 23 withHooks({ 24
  48. export const ProductsStore = signalStore( withComputed((store, globalProductsStore = inject(GlobalProductsStore)) =>

    ({ productsByCategories: computed(() => { const products = globalProductsStore.products(); const productsByCategory = ...; const categories = Object.keys(productsByCategory); return // ...; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), router = inject(Router) ) => ({ addToCart: globalCheckoutStore.addToCart, onProductClicked(id: string): void { router.navigate(['products', id]); } }) ), withHooks({ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ export const ProductsStore = signalStore( 1 2 productsByCategories: computed(() => { 3 const products = globalProductsStore.products(); 4 const productsByCategory = ...; 5 6 const categories = Object.keys(productsByCategory); 7 8 return // ...; 9 }) 10 })), 11 withMethods( 12 ( 13 store, 14 globalCheckoutStore = inject(GlobalCheckoutStore), 15 router = inject(Router) 16 ) => ({ 17 addToCart: globalCheckoutStore.addToCart, 18 onProductClicked(id: string): void { 19 router.navigate(['products', id]); 20 } 21 }) 22 ), 23 withHooks({ 24 withHooks({ onInit(_, globalProductsStore = inject(GlobalProductsStore)) { globalProductsStore.getAll(); } }) 6 const categories = Object.keys(productsByCategory); 7 8 return // ...; 9 }) 10 })), 11 withMethods( 12 ( 13 store, 14 globalCheckoutStore = inject(GlobalCheckoutStore), 15 router = inject(Router) 16 ) => ({ 17 addToCart: globalCheckoutStore.addToCart, 18 onProductClicked(id: string): void { 19 router.navigate(['products', id]); 20 } 21 }) 22 ), 23 24 25 26 27 28 ); 29
  49. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
  50. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24
  51. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 5 6 7 8 9 10 11 12 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24
  52. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 5 6 7 8 9 10 11 12 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 loadProductIfNotLoaded: rxMethod<string>( 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 filter((productId) => !globalProductsStore.products() 25 .find(x => x.id === productId)), 26 exhaustMap((id) => 27 productDetailService.loadProductDetail(id).pipe( 28 tapResponse({ 29 next: (product) => globalProductsStore.add(product), 30 error: console.error 31 }) 32 ) 33
  53. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 5 6 7 8 9 10 11 12 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 loadProductIfNotLoaded: rxMethod<string>( export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 tap((productId) => patchState(store, { productId })), }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 24 filter((productId) => !globalProductsStore.products() 25 .find(x => x.id === productId)), 26 exhaustMap((id) => 27 productDetailService.loadProductDetail(id).pipe( 28 tapResponse({ 29 next: (product) => globalProductsStore.add(product), 30 error: console.error 31 }) 32 ) 33 ) 34 ) 35
  54. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 5 6 7 8 9 10 11 12 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 loadProductIfNotLoaded: rxMethod<string>( export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 tap((productId) => patchState(store, { productId })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 24 filter((productId) => !globalProductsStore.products() .find(x => x.id === productId)), withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 25 26 exhaustMap((id) => 27 productDetailService.loadProductDetail(id).pipe( 28 tapResponse({ 29 next: (product) => globalProductsStore.add(product), 30 error: console.error 31 }) 32 ) 33 ) 34 ) 35 ) 36 }) 37
  55. export const ProductDetailStore = signalStore( withState({ productId: null as string

    | null }), withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), withMethods( ( store, globalCheckoutStore = inject(GlobalCheckoutStore), globalProductsStore = inject(GlobalProductsStore), productDetailService = inject(ProductDetailService) ) => ({ addToCart: globalCheckoutStore.addToCart, loadProductIfNotLoaded: rxMethod<string>( pipe( tap((productId) => patchState(store, { productId })), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 withState({ productId: null as string | null }), export const ProductDetailStore = signalStore( 1 2 3 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ productDetail: computed(() => { const productId = store.productId(); const existingProduct = globalProductsStore.products() .find(x => x.id === productId); return existingProduct ?? null; }) })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 5 6 7 8 9 10 11 12 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 loadProductIfNotLoaded: rxMethod<string>( export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 tap((productId) => patchState(store, { productId })), export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 24 export const ProductDetailStore = signalStore( 1 withState({ 2 productId: null as string | null 3 }), 4 withComputed((store, globalProductsStore = inject(GlobalProductsStore)) => ({ 5 productDetail: computed(() => { 6 const productId = store.productId(); 7 const existingProduct = globalProductsStore.products() 8 .find(x => x.id === productId); 9 10 return existingProduct ?? null; 11 }) 12 })), 13 withMethods( 14 ( 15 store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 exhaustMap((id) => productDetailService.loadProductDetail(id).pipe( tapResponse({ next: (product) => globalProductsStore.add(product), error: console.error }) ) ) store, 16 globalCheckoutStore = inject(GlobalCheckoutStore), 17 globalProductsStore = inject(GlobalProductsStore), 18 productDetailService = inject(ProductDetailService) 19 ) => ({ 20 addToCart: globalCheckoutStore.addToCart, 21 loadProductIfNotLoaded: rxMethod<string>( 22 pipe( 23 tap((productId) => patchState(store, { productId })), 24 filter((productId) => !globalProductsStore.products() 25 .find(x => x.id === productId)), 26 27 28 29 30 31 32 33 34 ) 35 ) 36 }) 37 ) 38 ); 39
  56. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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
  57. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25
  58. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 7 8 9 10 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25
  59. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 7 8 9 10 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 12 13 14 15 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25
  60. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 7 8 9 10 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 12 13 14 15 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 17 18 19 20 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25
  61. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 7 8 9 10 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 12 13 14 15 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 17 18 19 20 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 22 23 24 25
  62. src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/...

    │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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 ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ src/ 1 2 3 4 5 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 7 8 9 10 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 12 13 14 15 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 17 18 19 20 21 └── shared/ 22 └── store/ 23 ├── global-checkout.store.ts 24 └── global-products.store.ts 25 └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts src/ 1 ├── products/ 2 │ ├── container/ 3 │ │ ├── products.component.ts/html/... 4 │ │ └── products.store.ts 5 │ └── presentational/ 6 ├── product-details/ 7 │ ├── container/ 8 │ │ ├── product-detail.component.ts/html/... 9 │ │ └── product-detail.store.ts 10 │ └── presentational/ 11 ├── checkout/ 12 │ ├── container/ 13 │ │ ├── checkout.component.ts/html/... 14 │ │ └── checkout.store.ts 15 │ └── presentational/ 16 ├── shell/ 17 │ ├── container/ 18 │ │ ├── shell.component.ts/html/... 19 │ │ └── shell.store.ts 20 │ └── presentational/ 21 22 23 24 25 src/ ├── products/ │ ├── container/ │ │ ├── products.component.ts/html/... │ │ └── products.store.ts │ └── presentational/ ├── product-details/ │ ├── container/ │ │ ├── product-detail.component.ts/html/... │ │ └── product-detail.store.ts │ └── presentational/ ├── checkout/ │ ├── container/ │ │ ├── checkout.component.ts/html/... │ │ └── checkout.store.ts │ └── presentational/ ├── shell/ │ ├── container/ │ │ ├── shell.component.ts/html/... │ │ └── shell.store.ts │ └── presentational/ └── shared/ └── store/ ├── global-checkout.store.ts └── global-products.store.ts 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