Share
Embed
Why State is the Most Important Part of Your Angular Application
State is the foundation of every modern Angular application. A well-structured state management strategy determines not only how data flows through an application but also how scalable, maintainable, and performant it becomes. Without proper state management, applications quickly become hard to debug, difficult to scale, and prone to inconsistencies. This talk explores why state is the most crucial part of an Angular application, how to choose the right state management approach, and what common pitfalls to avoid. Whether using NgRx, Akita, Signals, or a simple service-based approach, understanding and structuring state effectively is the key to long-term success.
Fabian Gosebrink
June 20, 2025
Transcript
86 slides
-
@Component({ /* ... */ }) export class ProductsComponent { private
readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); products: Product[] = []; this.productsService.loadProducts() .subscribe((products) => this.products = products); onCartClicked(product: Product): void { this.checkoutService.addToCart(product).subscribe(() => { this.toastrService.success('Item Added to Cart'); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -
@Component({ /* ... */ }) export class ProductsComponent { private
readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); products: Product[] = []; this.productsService.loadProducts() .subscribe((products) => this.products = products); onCartClicked(product: Product): void { this.checkoutService.addToCart(product).subscribe(() => { this.toastrService.success('Item Added to Cart'); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private readonly productsService = inject(ProductsService); products: Product[] = []; this.productsService.loadProducts() .subscribe((products) => this.products = products); @Component({ /* ... */ }) 1 export class ProductsComponent { 2 3 private readonly checkoutService = inject(CheckoutService); 4 5 6 7 8 9 10 onCartClicked(product: Product): void { 11 this.checkoutService.addToCart(product).subscribe(() => { 12 this.toastrService.success('Item Added to Cart'); 13 }); 14 } 15 } 16 -
@Component({ /* ... */ }) export class ProductsComponent { private
readonly productsService = inject(ProductsService); private readonly checkoutService = inject(CheckoutService); products: Product[] = []; this.productsService.loadProducts() .subscribe((products) => this.products = products); onCartClicked(product: Product): void { this.checkoutService.addToCart(product).subscribe(() => { this.toastrService.success('Item Added to Cart'); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private readonly productsService = inject(ProductsService); products: Product[] = []; this.productsService.loadProducts() .subscribe((products) => this.products = products); @Component({ /* ... */ }) 1 export class ProductsComponent { 2 3 private readonly checkoutService = inject(CheckoutService); 4 5 6 7 8 9 10 onCartClicked(product: Product): void { 11 this.checkoutService.addToCart(product).subscribe(() => { 12 this.toastrService.success('Item Added to Cart'); 13 }); 14 } 15 } 16 private readonly checkoutService = inject(CheckoutService); onCartClicked(product: Product): void { this.checkoutService.addToCart(product).subscribe(() => { this.toastrService.success('Item Added to Cart'); }); @Component({ /* ... */ }) 1 export class ProductsComponent { 2 private readonly productsService = inject(ProductsService); 3 4 5 products: Product[] = []; 6 7 this.productsService.loadProducts() 8 .subscribe((products) => this.products = products); 9 10 11 12 13 14 } 15 } 16 -
export class CheckoutService { readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged
= this.#cartProductsChanged.asObservable(); addToCart(product: Product) { return this.#http.post<Product[]>(this.#baseUrl, product) .pipe(map((products) => { this.#cartProductsChanged.next(products); return products; }), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 -
export class CheckoutService { readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged
= this.#cartProductsChanged.asObservable(); addToCart(product: Product) { return this.#http.post<Product[]>(this.#baseUrl, product) .pipe(map((products) => { this.#cartProductsChanged.next(products); return products; }), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 readonly #cartProductsChanged = new Subject<Product[]>(); cartProductsChanged = this.#cartProductsChanged.asObservable(); this.#cartProductsChanged.next(products); export class CheckoutService { 1 2 3 4 addToCart(product: Product) { 5 return this.#http.post<Product[]>(this.#baseUrl, product) 6 .pipe(map((products) => { 7 8 9 return products; 10 }), 11 ); 12 } 13 } 14 -
@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 -
@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 <app-header [cartProductsCount]="cartProductsCount()" /> cartProductsCount = signal(0); @Component({ 1 template: ` 2 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 this.checkoutService.getCartProducts().subscribe((products) => { 11 this.cartProductsCount.set(products.length); 12 }); 13 14 this.checkoutService.cartProductsChanged.subscribe((products) => { 15 this.cartProductsCount.set(products.length); 16 }); 17 } 18 } 19 -
@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 <app-header [cartProductsCount]="cartProductsCount()" /> cartProductsCount = signal(0); @Component({ 1 template: ` 2 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 this.checkoutService.getCartProducts().subscribe((products) => { 11 this.cartProductsCount.set(products.length); 12 }); 13 14 this.checkoutService.cartProductsChanged.subscribe((products) => { 15 this.cartProductsCount.set(products.length); 16 }); 17 } 18 } 19 this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); @Component({ 1 template: ` 2 <app-header [cartProductsCount]="cartProductsCount()" /> 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 cartProductsCount = signal(0); 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 11 12 13 14 this.checkoutService.cartProductsChanged.subscribe((products) => { 15 this.cartProductsCount.set(products.length); 16 }); 17 } 18 } 19 -
@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 <app-header [cartProductsCount]="cartProductsCount()" /> cartProductsCount = signal(0); @Component({ 1 template: ` 2 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 this.checkoutService.getCartProducts().subscribe((products) => { 11 this.cartProductsCount.set(products.length); 12 }); 13 14 this.checkoutService.cartProductsChanged.subscribe((products) => { 15 this.cartProductsCount.set(products.length); 16 }); 17 } 18 } 19 this.checkoutService.getCartProducts().subscribe((products) => { this.cartProductsCount.set(products.length); }); @Component({ 1 template: ` 2 <app-header [cartProductsCount]="cartProductsCount()" /> 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 cartProductsCount = signal(0); 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 11 12 13 14 this.checkoutService.cartProductsChanged.subscribe((products) => { 15 this.cartProductsCount.set(products.length); 16 }); 17 } 18 } 19 this.checkoutService.cartProductsChanged.subscribe((products) => { this.cartProductsCount.set(products.length); }); @Component({ 1 template: ` 2 <app-header [cartProductsCount]="cartProductsCount()" /> 3 `, 4 }) 5 export class ShellComponent implements OnInit { 6 cartProductsCount = signal(0); 7 private readonly checkoutService = inject(CheckoutService); 8 9 ngOnInit() { 10 this.checkoutService.getCartProducts().subscribe((products) => { 11 this.cartProductsCount.set(products.length); 12 }); 13 14 15 16 17 } 18 } 19 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ // ... cartProducts = signal<Product[]>([]); totalAmount = signal(0); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ // ... cartProducts = signal<Product[]>([]); totalAmount = signal(0); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 4 5 6 ngOnInit(): void { 7 merge( 8 this.checkoutService.getCartProducts(), 9 this.checkoutService.cartProductsChanged, 10 ).subscribe((products) => { 11 this.cartProducts.set(products); 12 const totalAmount = this.calculateTotalAmount(products); 13 this.totalAmount.set(totalAmount); 14 }); 15 } 16 } 17 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ // ... cartProducts = signal<Product[]>([]); totalAmount = signal(0); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 4 5 6 ngOnInit(): void { 7 merge( 8 this.checkoutService.getCartProducts(), 9 this.checkoutService.cartProductsChanged, 10 ).subscribe((products) => { 11 this.cartProducts.set(products); 12 const totalAmount = this.calculateTotalAmount(products); 13 this.totalAmount.set(totalAmount); 14 }); 15 } 16 } 17 merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 cartProducts = signal<Product[]>([]); 4 totalAmount = signal(0); 5 6 ngOnInit(): void { 7 8 9 10 ).subscribe((products) => { 11 this.cartProducts.set(products); 12 const totalAmount = this.calculateTotalAmount(products); 13 this.totalAmount.set(totalAmount); 14 }); 15 } 16 } 17 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ // ... cartProducts = signal<Product[]>([]); totalAmount = signal(0); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cartProducts = signal<Product[]>([]); totalAmount = signal(0); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 4 5 6 ngOnInit(): void { 7 merge( 8 this.checkoutService.getCartProducts(), 9 this.checkoutService.cartProductsChanged, 10 ).subscribe((products) => { 11 this.cartProducts.set(products); 12 const totalAmount = this.calculateTotalAmount(products); 13 this.totalAmount.set(totalAmount); 14 }); 15 } 16 } 17 merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 cartProducts = signal<Product[]>([]); 4 totalAmount = signal(0); 5 6 ngOnInit(): void { 7 8 9 10 ).subscribe((products) => { 11 this.cartProducts.set(products); 12 const totalAmount = this.calculateTotalAmount(products); 13 this.totalAmount.set(totalAmount); 14 }); 15 } 16 } 17 ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); @Component({ /* ... */ }) 1 export class CheckoutComponent implements OnInit { 2 // ... 3 cartProducts = signal<Product[]>([]); 4 totalAmount = signal(0); 5 6 ngOnInit(): void { 7 merge( 8 this.checkoutService.getCartProducts(), 9 this.checkoutService.cartProductsChanged, 10 11 12 13 14 15 } 16 } 17 -
Header/Shell - Reads State Manipulate State /products Has State Checkout
Service Reads State /checkout Manipulate State Reads State -
@Injectable({ providedIn: 'root' }) export class CheckoutService { readonly cartProducts
= signal<Product[]>([]); readonly cartProductCount = computed(() => this.cartProducts().length); readonly totalAmount = computed(() => this.cartProducts() .reduce((acc: number, prev) => acc + prev.price, 0), ); addToCart(product: Product) { // ... } // more functions } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -
@Injectable({ providedIn: 'root' }) export class CheckoutService { readonly cartProducts
= signal<Product[]>([]); readonly cartProductCount = computed(() => this.cartProducts().length); readonly totalAmount = computed(() => this.cartProducts() .reduce((acc: number, prev) => acc + prev.price, 0), ); addToCart(product: Product) { // ... } // more functions } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 readonly cartProducts = signal<Product[]>([]); readonly cartProductCount = computed(() => this.cartProducts().length); readonly totalAmount = computed(() => this.cartProducts() .reduce((acc: number, prev) => acc + prev.price, 0), ); @Injectable({ providedIn: 'root' }) 1 export class CheckoutService { 2 3 4 5 6 7 8 9 10 11 12 addToCart(product: Product) { 13 // ... 14 } 15 16 // more functions 17 } 18 -
@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 -
@Component({ template: ` <app-header [cartProductsCount]="cartProductsCount()" /> `, }) export class
ShellComponent implements OnInit { readonly cartProductsCount = inject(CheckoutService).cartProductCount; } 1 2 3 4 5 6 7 8 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ // ... cartProducts = signal<Product[]>([]); totalAmount = signal(0); ngOnInit(): void { merge( this.checkoutService.getCartProducts(), this.checkoutService.cartProductsChanged, ).subscribe((products) => { this.cartProducts.set(products); const totalAmount = this.calculateTotalAmount(products); this.totalAmount.set(totalAmount); }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -
@Component({ /* ... */ }) export class CheckoutComponent implements OnInit
{ readonly #checkoutService = inject(CheckoutService); readonly cartProducts = this.#checkoutService.cartProducts; readonly totalAmount = this.#checkoutService.totalAmount; ngOnInit(): void { this.checkoutService.getCartProducts(); } } 1 2 3 4 5 6 7 8 9 10 11 -
src/ └── app/ ├── features/ │ ├── checkout/ │ │
└── ... │ └── products/ │ └── ... └── shared/ └── auth/ └── ... 1 2 3 4 5 6 7 8 9 10 -
{ "product": { // ... }, "checkout": { // ...
}, "shared": { ... }, } 1 2 3 4 5 6 7 8 9 10 11 export const APP_ROUTES: Routes = [ { path: 'product', loadChildren: () => // ... }, { path: 'checkout', loadChildren: () => // ... }, ]; 1 2 3 4 5 6 7 8 9 10 -
Service A Service B Service C Service C ... Feature
State Component State Component State -
Service A Service B Service C Service C ... WebSockets
Component State Component State Feature State -
@Injectable({ providedIn: 'root' }) export class CheckoutService { readonly #baseUrl
= 'http://localhost:3000/cart/'; readonly #http = inject(HttpClient); addToCart(product: Product) { return this.#http.post<Product[]>(this.#baseUrl, product); } getCartProducts() { return this.#http.get<Product[]>(this.#baseUrl); } removeFromCart(index: number) { return this.#http.delete(this.#baseUrl + index); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 -
export const CheckoutStore = signalStore( { providedIn: 'root' }, withState<CheckoutState>({
products: [] as Product[]}), withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), withHooks({ onInit(store) { store.loadAll(); }, }), ); 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 -
export const CheckoutStore = signalStore( { providedIn: 'root' }, withState<CheckoutState>({
products: [] as Product[]}), withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), withHooks({ onInit(store) { store.loadAll(); }, }), ); 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 withState<CheckoutState>({ products: [] as Product[]}), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 -
export const CheckoutStore = signalStore( { providedIn: 'root' }, withState<CheckoutState>({
products: [] as Product[]}), withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), withHooks({ onInit(store) { store.loadAll(); }, }), ); 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 withState<CheckoutState>({ products: [] as Product[]}), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 4 5 6 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 -
export const CheckoutStore = signalStore( { providedIn: 'root' }, withState<CheckoutState>({
products: [] as Product[]}), withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), withHooks({ onInit(store) { store.loadAll(); }, }), ); 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 withState<CheckoutState>({ products: [] as Product[]}), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 4 5 6 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 8 9 10 11 12 13 14 15 16 17 18 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 -
export const CheckoutStore = signalStore( { providedIn: 'root' }, withState<CheckoutState>({
products: [] as Product[]}), withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), withHooks({ onInit(store) { store.loadAll(); }, }), ); 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 withState<CheckoutState>({ products: [] as Product[]}), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withComputed((store) => ({ cartProductsCount: computed(() => store.products().length), totalAmount: computed(() => { /* ... */ }), })), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 4 5 6 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withMethods((store, checkoutService = inject(CheckoutService)) => ({ loadAll: rxMethod<void>( exhaustMap(() => checkoutService.getCartProducts().pipe( tapResponse({ next: (products) => patchState(store, { products }), }), ), ), ), // ... })), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 8 9 10 11 12 13 14 15 16 17 18 19 withHooks({ 20 onInit(store) { 21 store.loadAll(); 22 }, 23 }), 24 ); 25 withHooks({ onInit(store) { store.loadAll(); }, }), export const CheckoutStore = signalStore( 1 { providedIn: 'root' }, 2 withState<CheckoutState>({ products: [] as Product[]}), 3 withComputed((store) => ({ 4 cartProductsCount: computed(() => store.products().length), 5 totalAmount: computed(() => { /* ... */ }), 6 })), 7 withMethods((store, checkoutService = inject(CheckoutService)) => ({ 8 loadAll: rxMethod<void>( 9 exhaustMap(() => 10 checkoutService.getCartProducts().pipe( 11 tapResponse({ 12 next: (products) => patchState(store, { products }), 13 }), 14 ), 15 ), 16 ), 17 // ... 18 })), 19 20 21 22 23 24 ); 25 -
@Component({ /* ... */ }) export class CheckoutComponent { readonly
store = inject(CheckoutStore); } 1 2 3 4 -
@Component({ template: ` <app-header [cartProductsCount]="store.cartProductsCount()" /> `, }) export class
ShellComponent { readonly store = inject(CheckoutStore); } 1 2 3 4 5 6 7 8 -
@Component({ template: ` @for ( productCategory of store.productsByCategories(); track ...)
{ //... } `, providers: [ProductsStore], }) export class ProductsComponent { readonly store = inject(ProductsStore); // using store if needed } 1 2 3 4 5 6 7 8 9 10 11 12 13 -
The real bug was the way we thought about state
— or didn’t.