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

NgRx Signal Store - A Deeper Dive

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

NgRx Signal Store - A Deeper Dive

Avatar for Fabian Gosebrink

Fabian Gosebrink

March 14, 2026
Tweet

More Decks by Fabian Gosebrink

Other Decks in Technology

Transcript

  1. signalStore( withState(), withComputed(), withMethods( patchState() ) ); 1 2 3

    4 5 6 7 signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7
  2. signalStore( withState(), withComputed(), withMethods( patchState() ) ); 1 2 3

    4 5 6 7 signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withState(), signalStore( 1 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7
  3. signalStore( withState(), withComputed(), withMethods( patchState() ) ); 1 2 3

    4 5 6 7 signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withState(), signalStore( 1 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withComputed(), signalStore( 1 withState(), 2 3 withMethods( 4 patchState() 5 ) 6 ); 7
  4. signalStore( withState(), withComputed(), withMethods( patchState() ) ); 1 2 3

    4 5 6 7 signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withState(), signalStore( 1 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withComputed(), signalStore( 1 withState(), 2 3 withMethods( 4 patchState() 5 ) 6 ); 7 withMethods( signalStore( 1 withState(), 2 withComputed(), 3 4 patchState() 5 ) 6 ); 7
  5. signalStore( withState(), withComputed(), withMethods( patchState() ) ); 1 2 3

    4 5 6 7 signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withState(), signalStore( 1 2 withComputed(), 3 withMethods( 4 patchState() 5 ) 6 ); 7 withComputed(), signalStore( 1 withState(), 2 3 withMethods( 4 patchState() 5 ) 6 ); 7 withMethods( signalStore( 1 withState(), 2 withComputed(), 3 4 patchState() 5 ) 6 ); 7 patchState() signalStore( 1 withState(), 2 withComputed(), 3 withMethods( 4 5 ) 6 ); 7
  6. const CounterStore = signalStore( withState({ count: 0 }), withComputed(({ count

    }) => ({ doubled: computed(() => count() * 2), })), withMethods((store) => ({ increment() { patchState(store, { count: store.count() + 1 }); } })) ); 1 2 3 4 5 6 7 8 9 10 11 12 13
  7. type BookingState = { booking: Booking } export const BookingStore

    = signalStore( //... withMethods((store, service = ...) => ({ loadBooking: rxMethod<number>( exhaustMap((id) => service.load(id).pipe( tapResponse({ next: (booking) => patchState(store, { booking }), error: (err) => console.error(err) }) )) }), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  8. type BookingState = { booking: Booking } export const BookingStore

    = signalStore( //... withMethods((store, service = ...) => ({ loadBooking: rxMethod<number>( exhaustMap((id) => service.load(id).pipe( tapResponse({ next: (booking) => patchState(store, { booking }), error: (err) => console.error(err) }) )) }), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 loadBooking: rxMethod<number>( type BookingState = { booking: Booking } 1 2 export const BookingStore = signalStore( 3 //... 4 withMethods((store, service = ...) => ({ 5 6 exhaustMap((id) => service.load(id).pipe( 7 tapResponse({ 8 next: (booking) => patchState(store, { booking }), 9 error: (err) => console.error(err) 10 }) 11 )) 12 }), 13 ); 14
  9. type BookingState = { booking: Booking } export const BookingStore

    = signalStore( //... withMethods((store, service = ...) => ({ loadBooking: rxMethod<number>( exhaustMap((id) => service.load(id).pipe( tapResponse({ next: (booking) => patchState(store, { booking }), error: (err) => console.error(err) }) )) }), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 loadBooking: rxMethod<number>( type BookingState = { booking: Booking } 1 2 export const BookingStore = signalStore( 3 //... 4 withMethods((store, service = ...) => ({ 5 6 exhaustMap((id) => service.load(id).pipe( 7 tapResponse({ 8 next: (booking) => patchState(store, { booking }), 9 error: (err) => console.error(err) 10 }) 11 )) 12 }), 13 ); 14 exhaustMap((id) => service.load(id).pipe( tapResponse({ next: (booking) => patchState(store, { booking }), error: (err) => console.error(err) }) )) type BookingState = { booking: Booking } 1 2 export const BookingStore = signalStore( 3 //... 4 withMethods((store, service = ...) => ({ 5 loadBooking: rxMethod<number>( 6 7 8 9 10 11 12 }), 13 ); 14
  10. @Component({ /* ... */ }) export class MyComponent { store

    = inject(BookingStore); constructor() { // executed one time store.loadBooking(42) // executed each time the signal changes const id = signal(42); store.loadBooking(id) // executed each time a new value is emitted const id$ = of(42, 43, 44); store.loadBooking(id$) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  11. @Component({ /* ... */ }) export class MyComponent { store

    = inject(BookingStore); constructor() { // executed one time store.loadBooking(42) // executed each time the signal changes const id = signal(42); store.loadBooking(id) // executed each time a new value is emitted const id$ = of(42, 43, 44); store.loadBooking(id$) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 store.loadBooking(42) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 7 8 // executed each time the signal changes 9 const id = signal(42); 10 store.loadBooking(id) 11 12 // executed each time a new value is emitted 13 const id$ = of(42, 43, 44); 14 store.loadBooking(id$) 15 } 16 } 17
  12. @Component({ /* ... */ }) export class MyComponent { store

    = inject(BookingStore); constructor() { // executed one time store.loadBooking(42) // executed each time the signal changes const id = signal(42); store.loadBooking(id) // executed each time a new value is emitted const id$ = of(42, 43, 44); store.loadBooking(id$) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 store.loadBooking(42) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 7 8 // executed each time the signal changes 9 const id = signal(42); 10 store.loadBooking(id) 11 12 // executed each time a new value is emitted 13 const id$ = of(42, 43, 44); 14 store.loadBooking(id$) 15 } 16 } 17 const id = signal(42); store.loadBooking(id) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 store.loadBooking(42) 7 8 // executed each time the signal changes 9 10 11 12 // executed each time a new value is emitted 13 const id$ = of(42, 43, 44); 14 store.loadBooking(id$) 15 } 16 } 17
  13. @Component({ /* ... */ }) export class MyComponent { store

    = inject(BookingStore); constructor() { // executed one time store.loadBooking(42) // executed each time the signal changes const id = signal(42); store.loadBooking(id) // executed each time a new value is emitted const id$ = of(42, 43, 44); store.loadBooking(id$) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 store.loadBooking(42) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 7 8 // executed each time the signal changes 9 const id = signal(42); 10 store.loadBooking(id) 11 12 // executed each time a new value is emitted 13 const id$ = of(42, 43, 44); 14 store.loadBooking(id$) 15 } 16 } 17 const id = signal(42); store.loadBooking(id) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 store.loadBooking(42) 7 8 // executed each time the signal changes 9 10 11 12 // executed each time a new value is emitted 13 const id$ = of(42, 43, 44); 14 store.loadBooking(id$) 15 } 16 } 17 const id$ = of(42, 43, 44); store.loadBooking(id$) @Component({ /* ... */ }) 1 export class MyComponent { 2 store = inject(BookingStore); 3 4 constructor() { 5 // executed one time 6 store.loadBooking(42) 7 8 // executed each time the signal changes 9 const id = signal(42); 10 store.loadBooking(id) 11 12 // executed each time a new value is emitted 13 14 15 } 16 } 17
  14. @Component({ /* ... */ }) export class SearchComponent { search

    = new FormControl('', { nonNullable: true }); searchChanged = outputFromObservable( this.search.valueChanges.pipe( debounceTime(300), distinctUntilChanged() ), ); } 1 2 3 4 5 6 7 8 9 10 11
  15. @Component({ selector: 'app-products', imports: [ProductCategoryComponent, SearchComponent], template: '<app-search (searchChanged)="store.searchValueChanged($event)" />',

    styleUrl: './products.component.scss', providers: [ProductsStore] }) export class ProductsComponent { store = inject(ProductsStore); constructor() { const query = this.store.searchTerm; this.store.loadByQuery(query); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  16. @Component({ selector: 'app-products', imports: [ProductCategoryComponent, SearchComponent], template: '<app-search (searchChanged)="store.searchValueChanged($event)" />',

    styleUrl: './products.component.scss', providers: [ProductsStore] }) export class ProductsComponent { store = inject(ProductsStore); constructor() { const query = this.store.searchTerm; this.store.loadByQuery(query); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (searchChanged)="store.searchValueChanged($event)" />', @Component({ 1 selector: 'app-products', 2 imports: [ProductCategoryComponent, SearchComponent], 3 template: '<app-search 4 5 styleUrl: './products.component.scss', 6 providers: [ProductsStore] 7 }) 8 export class ProductsComponent { 9 store = inject(ProductsStore); 10 11 constructor() { 12 const query = this.store.searchTerm; 13 this.store.loadByQuery(query); 14 } 15 } 16
  17. @Component({ selector: 'app-products', imports: [ProductCategoryComponent, SearchComponent], template: '<app-search (searchChanged)="store.searchValueChanged($event)" />',

    styleUrl: './products.component.scss', providers: [ProductsStore] }) export class ProductsComponent { store = inject(ProductsStore); constructor() { const query = this.store.searchTerm; this.store.loadByQuery(query); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (searchChanged)="store.searchValueChanged($event)" />', @Component({ 1 selector: 'app-products', 2 imports: [ProductCategoryComponent, SearchComponent], 3 template: '<app-search 4 5 styleUrl: './products.component.scss', 6 providers: [ProductsStore] 7 }) 8 export class ProductsComponent { 9 store = inject(ProductsStore); 10 11 constructor() { 12 const query = this.store.searchTerm; 13 this.store.loadByQuery(query); 14 } 15 } 16 const query = this.store.searchTerm; this.store.loadByQuery(query); @Component({ 1 selector: 'app-products', 2 imports: [ProductCategoryComponent, SearchComponent], 3 template: '<app-search 4 (searchChanged)="store.searchValueChanged($event)" />', 5 styleUrl: './products.component.scss', 6 providers: [ProductsStore] 7 }) 8 export class ProductsComponent { 9 store = inject(ProductsStore); 10 11 constructor() { 12 13 14 } 15 } 16
  18. export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(()

    => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13
  19. export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(()

    => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 withState({ searchTerm: '' }), searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, export const ProductsStore = signalStore( 1 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 8 9 10 }), 11 ), 12 ); 13
  20. export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(()

    => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 withState({ searchTerm: '' }), searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, export const ProductsStore = signalStore( 1 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 8 9 10 }), 11 ), 12 ); 13 loadByQuery: rxMethod<...>, export const ProductsStore = signalStore( 1 withState({ searchTerm: '' }), 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 7 searchValueChanged(searchTerm: string) { 8 patchState(store, { searchTerm }); 9 }, 10 }), 11 ), 12 ); 13
  21. export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(()

    => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 withState({ searchTerm: '' }), searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, export const ProductsStore = signalStore( 1 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 8 9 10 }), 11 ), 12 ); 13 loadByQuery: rxMethod<...>, export const ProductsStore = signalStore( 1 withState({ searchTerm: '' }), 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 7 searchValueChanged(searchTerm: string) { 8 patchState(store, { searchTerm }); 9 }, 10 }), 11 ), 12 ); 13 products: computed(() => { /* ... */ }), export const ProductsStore = signalStore( 1 withState({ searchTerm: '' }), 2 withComputed(() => ({ 3 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 searchValueChanged(searchTerm: string) { 8 patchState(store, { searchTerm }); 9 }, 10 }), 11 ), 12 ); 13
  22. export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(()

    => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 withState({ searchTerm: '' }), searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, export const ProductsStore = signalStore( 1 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 8 9 10 }), 11 ), 12 ); 13 loadByQuery: rxMethod<...>, export const ProductsStore = signalStore( 1 withState({ searchTerm: '' }), 2 withComputed(() => ({ 3 products: computed(() => { /* ... */ }), 4 })), 5 withMethods((store) => ({ 6 7 searchValueChanged(searchTerm: string) { 8 patchState(store, { searchTerm }); 9 }, 10 }), 11 ), 12 ); 13 products: computed(() => { /* ... */ }), export const ProductsStore = signalStore( 1 withState({ searchTerm: '' }), 2 withComputed(() => ({ 3 4 })), 5 withMethods((store) => ({ 6 loadByQuery: rxMethod<...>, 7 searchValueChanged(searchTerm: string) { 8 patchState(store, { searchTerm }); 9 }, 10 }), 11 ), 12 ); 13 export const ProductsStore = signalStore( withState({ searchTerm: '' }), withComputed(() => ({ products: computed(() => { /* ... */ }), })), withMethods((store) => ({ loadByQuery: rxMethod<...>, searchValueChanged(searchTerm: string) { patchState(store, { searchTerm }); }, }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13
  23. export function withMyNameFeature() { return signalStoreFeature( withState({ firstName: ‘Fabian’, lastName:

    ‘Gosebrink’ }), withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), withMethods(({firstName, lastName }) => ({ logName() { console.log(firstName(), lastName()); } })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
  24. export function withMyNameFeature() { return signalStoreFeature( withState({ firstName: ‘Fabian’, lastName:

    ‘Gosebrink’ }), withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), withMethods(({firstName, lastName }) => ({ logName() { console.log(firstName(), lastName()); } })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 withState({ firstName: ‘Fabian’, lastName: ‘Gosebrink’ }), export function withMyNameFeature() { 1 return signalStoreFeature( 2 3 4 5 6 7 withComputed(({firstName, lastName }) => ({ 8 fullName: computed(() => `${firstName()} ${lastName()}`), 9 })), 10 11 withMethods(({firstName, lastName }) => ({ 12 logName() { 13 console.log(firstName(), lastName()); 14 } 15 })) 16 17 ); 18 } 19
  25. export function withMyNameFeature() { return signalStoreFeature( withState({ firstName: ‘Fabian’, lastName:

    ‘Gosebrink’ }), withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), withMethods(({firstName, lastName }) => ({ logName() { console.log(firstName(), lastName()); } })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 withState({ firstName: ‘Fabian’, lastName: ‘Gosebrink’ }), export function withMyNameFeature() { 1 return signalStoreFeature( 2 3 4 5 6 7 withComputed(({firstName, lastName }) => ({ 8 fullName: computed(() => `${firstName()} ${lastName()}`), 9 })), 10 11 withMethods(({firstName, lastName }) => ({ 12 logName() { 13 console.log(firstName(), lastName()); 14 } 15 })) 16 17 ); 18 } 19 withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), export function withMyNameFeature() { 1 return signalStoreFeature( 2 withState({ 3 firstName: ‘Fabian’, 4 lastName: ‘Gosebrink’ 5 }), 6 7 8 9 10 11 withMethods(({firstName, lastName }) => ({ 12 logName() { 13 console.log(firstName(), lastName()); 14 } 15 })) 16 17 ); 18 } 19
  26. export function withMyNameFeature() { return signalStoreFeature( withState({ firstName: ‘Fabian’, lastName:

    ‘Gosebrink’ }), withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), withMethods(({firstName, lastName }) => ({ logName() { console.log(firstName(), lastName()); } })) ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 withState({ firstName: ‘Fabian’, lastName: ‘Gosebrink’ }), export function withMyNameFeature() { 1 return signalStoreFeature( 2 3 4 5 6 7 withComputed(({firstName, lastName }) => ({ 8 fullName: computed(() => `${firstName()} ${lastName()}`), 9 })), 10 11 withMethods(({firstName, lastName }) => ({ 12 logName() { 13 console.log(firstName(), lastName()); 14 } 15 })) 16 17 ); 18 } 19 withComputed(({firstName, lastName }) => ({ fullName: computed(() => `${firstName()} ${lastName()}`), })), export function withMyNameFeature() { 1 return signalStoreFeature( 2 withState({ 3 firstName: ‘Fabian’, 4 lastName: ‘Gosebrink’ 5 }), 6 7 8 9 10 11 withMethods(({firstName, lastName }) => ({ 12 logName() { 13 console.log(firstName(), lastName()); 14 } 15 })) 16 17 ); 18 } 19 withMethods(({firstName, lastName }) => ({ logName() { console.log(firstName(), lastName()); } })) export function withMyNameFeature() { 1 return signalStoreFeature( 2 withState({ 3 firstName: ‘Fabian’, 4 lastName: ‘Gosebrink’ 5 }), 6 7 withComputed(({firstName, lastName }) => ({ 8 fullName: computed(() => `${firstName()} ${lastName()}`), 9 })), 10 11 12 13 14 15 16 17 ); 18 } 19
  27. export const MyStore = signalStore( withState({ myProp: 99 }), withMyNameFeature(),

    ); // Usage readonly store = inject(MyStore); store.firstName(); store.lastName(); store.fullName(); store.logName(); store.myProp(); 1 2 3 4 5 6 7 8 9 10 11 12 13
  28. export const MyStore = signalStore( withState({ myProp: 99 }), withMyNameFeature(),

    ); // Usage readonly store = inject(MyStore); store.firstName(); store.lastName(); store.fullName(); store.logName(); store.myProp(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withMyNameFeature(), export const MyStore = signalStore( 1 withState({ myProp: 99 }), 2 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 store.firstName(); 9 store.lastName(); 10 store.fullName(); 11 store.logName(); 12 store.myProp(); 13
  29. export const MyStore = signalStore( withState({ myProp: 99 }), withMyNameFeature(),

    ); // Usage readonly store = inject(MyStore); store.firstName(); store.lastName(); store.fullName(); store.logName(); store.myProp(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withMyNameFeature(), export const MyStore = signalStore( 1 withState({ myProp: 99 }), 2 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 store.firstName(); 9 store.lastName(); 10 store.fullName(); 11 store.logName(); 12 store.myProp(); 13 store.firstName(); store.lastName(); store.fullName(); store.logName(); export const MyStore = signalStore( 1 withState({ myProp: 99 }), 2 withMyNameFeature(), 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 9 10 11 12 store.myProp(); 13
  30. export const MyStore = signalStore( withState({ myProp: 99 }), withMyNameFeature(),

    ); // Usage readonly store = inject(MyStore); store.firstName(); store.lastName(); store.fullName(); store.logName(); store.myProp(); 1 2 3 4 5 6 7 8 9 10 11 12 13 withMyNameFeature(), export const MyStore = signalStore( 1 withState({ myProp: 99 }), 2 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 store.firstName(); 9 store.lastName(); 10 store.fullName(); 11 store.logName(); 12 store.myProp(); 13 store.firstName(); store.lastName(); store.fullName(); store.logName(); export const MyStore = signalStore( 1 withState({ myProp: 99 }), 2 withMyNameFeature(), 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 9 10 11 12 store.myProp(); 13 withState({ myProp: 99 }), store.myProp(); export const MyStore = signalStore( 1 2 withMyNameFeature(), 3 ); 4 5 // Usage 6 readonly store = inject(MyStore); 7 8 store.firstName(); 9 store.lastName(); 10 store.fullName(); 11 store.logName(); 12 13
  31. function withLogger<S extends object>() { return signalStoreFeature( withHooks({ onInit(store) {

    effect(() => console.log(getState(store))); } }) ); } 1 2 3 4 5 6 7 8 9
  32. type Booking = { id: string; customerName: string; date: string;

    }; // Usage export const BookingStore = signalStore( withEntities<Booking>(), // ... ); readonly store = inject(BookingStore); // automatically added store.ids(); store.entityMap(); store.entities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  33. type Booking = { id: string; customerName: string; date: string;

    }; // Usage export const BookingStore = signalStore( withEntities<Booking>(), // ... ); readonly store = inject(BookingStore); // automatically added store.ids(); store.entityMap(); store.entities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withEntities<Booking>(), type Booking = { 1 id: string; 2 customerName: string; 3 date: string; 4 }; 5 6 // Usage 7 export const BookingStore = signalStore( 8 9 // ... 10 ); 11 12 readonly store = inject(BookingStore); 13 14 // automatically added 15 store.ids(); 16 store.entityMap(); 17 store.entities(); 18
  34. type Booking = { id: string; customerName: string; date: string;

    }; // Usage export const BookingStore = signalStore( withEntities<Booking>(), // ... ); readonly store = inject(BookingStore); // automatically added store.ids(); store.entityMap(); store.entities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 withEntities<Booking>(), type Booking = { 1 id: string; 2 customerName: string; 3 date: string; 4 }; 5 6 // Usage 7 export const BookingStore = signalStore( 8 9 // ... 10 ); 11 12 readonly store = inject(BookingStore); 13 14 // automatically added 15 store.ids(); 16 store.entityMap(); 17 store.entities(); 18 store.ids(); store.entityMap(); store.entities(); type Booking = { 1 id: string; 2 customerName: string; 3 date: string; 4 }; 5 6 // Usage 7 export const BookingStore = signalStore( 8 withEntities<Booking>(), 9 // ... 10 ); 11 12 readonly store = inject(BookingStore); 13 14 // automatically added 15 16 17 18
  35. { 'first-booking-id': { id: 'first-booking-id', customerName: 'John Doe', date: '2025-01-15',

    }, 'second-booking-id': { id: 'second-booking-id', customerName: 'Jane Smith', date: '2025-02-03', }, } 1 2 3 4 5 6 7 8 9 10 11 12
  36. export const BookingStore = signalStore( // ... withEntities<Booking>(), withComputed((store) =>

    ({ bookingCount: computed(() => store.entities().length), })), ); 1 2 3 4 5 6 7 8
  37. export const BookingStore = signalStore( withEntities<Booking>(), // ... withMethods((store) =>

    ({ addBookings(bookings: Booking[]) { addEntities(store, bookings); }, updateBooking(booking: Booking) { updateEntity(store, booking); }, removeBooking(id: string) { removeEntity(store, id); }, })), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
  38. type Booking = { ... }; type Customer = {

    ... }; // Usage export const BookingStore = signalStore( withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), // ... ); readonly store = inject(BookingStore); // automatically added store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  39. type Booking = { ... }; type Customer = {

    ... }; // Usage export const BookingStore = signalStore( withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), // ... ); readonly store = inject(BookingStore); // automatically added store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), type Booking = { ... }; 1 type Customer = { ... }; 2 3 4 // Usage 5 export const BookingStore = signalStore( 6 7 8 // ... 9 ); 10 11 readonly store = inject(BookingStore); 12 13 // automatically added 14 store.bookingIds(); 15 store.bookingEntityMap(); 16 store.bookingEntities(); 17 18 store.customerIds(); 19 store.customerEntityMap(); 20 store.customerEntities(); 21
  40. type Booking = { ... }; type Customer = {

    ... }; // Usage export const BookingStore = signalStore( withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), // ... ); readonly store = inject(BookingStore); // automatically added store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), type Booking = { ... }; 1 type Customer = { ... }; 2 3 4 // Usage 5 export const BookingStore = signalStore( 6 7 8 // ... 9 ); 10 11 readonly store = inject(BookingStore); 12 13 // automatically added 14 store.bookingIds(); 15 store.bookingEntityMap(); 16 store.bookingEntities(); 17 18 store.customerIds(); 19 store.customerEntityMap(); 20 store.customerEntities(); 21 store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); type Booking = { ... }; 1 type Customer = { ... }; 2 3 4 // Usage 5 export const BookingStore = signalStore( 6 withEntities({ entity: type<Booking>(), collection: 'booking' }), 7 withEntities({ entity: type<Customer>(), collection: 'customer' }), 8 // ... 9 ); 10 11 readonly store = inject(BookingStore); 12 13 // automatically added 14 15 16 17 18 19 20 21
  41. type Booking = { ... }; type Customer = {

    ... }; // Usage export const BookingStore = signalStore( withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), // ... ); readonly store = inject(BookingStore); // automatically added store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), type Booking = { ... }; 1 type Customer = { ... }; 2 3 4 // Usage 5 export const BookingStore = signalStore( 6 7 8 // ... 9 ); 10 11 readonly store = inject(BookingStore); 12 13 // automatically added 14 store.bookingIds(); 15 store.bookingEntityMap(); 16 store.bookingEntities(); 17 18 store.customerIds(); 19 store.customerEntityMap(); 20 store.customerEntities(); 21 store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); type Booking = { ... }; 1 type Customer = { ... }; 2 3 4 // Usage 5 export const BookingStore = signalStore( 6 withEntities({ entity: type<Booking>(), collection: 'booking' }), 7 withEntities({ entity: type<Customer>(), collection: 'customer' }), 8 // ... 9 ); 10 11 readonly store = inject(BookingStore); 12 13 // automatically added 14 15 16 17 18 19 20 21 type Booking = { ... }; type Customer = { ... }; // Usage export const BookingStore = signalStore( withEntities({ entity: type<Booking>(), collection: 'booking' }), withEntities({ entity: type<Customer>(), collection: 'customer' }), // ... ); readonly store = inject(BookingStore); // automatically added store.bookingIds(); store.bookingEntityMap(); store.bookingEntities(); store.customerIds(); store.customerEntityMap(); store.customerEntities(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
  42. export const bookingApiEvents = eventGroup({ source: 'Bookings API', events: {

    bookingsLoaded: type<void>(), bookingsLoadedSuccess: type<Booking[]>(), }, }); export const bookingUserEvents = eventGroup({ source: 'Bookings User', events: { pageOpened: type<void>(), }, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14
  43. export const BookingStore = signalStore( // .. withEventHandlers((store, events =

    inject(Events), bookingService = inject(BookingService)) => ({ loadBookings$: events .on(bookingUserEvents.pageOpened) .pipe( exhaustMap(() => bookingService.loadBookings().pipe( mapResponse({ next: (bookings) => bookingApiEvents.bookingsLoadedSuccess(bookings), error: console.error, }), ), ), ), }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  44. export const BookingStore = signalStore( // .. withEventHandlers((store, events =

    inject(Events), bookingService = inject(BookingService)) => ({ loadBookings$: events .on(bookingUserEvents.pageOpened) .pipe( exhaustMap(() => bookingService.loadBookings().pipe( mapResponse({ next: (bookings) => bookingApiEvents.bookingsLoadedSuccess(bookings), error: console.error, }), ), ), ), }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 withEventHandlers((store, events = inject(Events), export const BookingStore = signalStore( 1 // .. 2 3 bookingService = inject(BookingService)) => ({ 4 loadBookings$: events 5 .on(bookingUserEvents.pageOpened) 6 .pipe( 7 exhaustMap(() => 8 bookingService.loadBookings().pipe( 9 mapResponse({ 10 next: (bookings) => 11 bookingApiEvents.bookingsLoadedSuccess(bookings), 12 error: console.error, 13 }), 14 ), 15 ), 16 ), 17 }), 18 ), 19 ); 20
  45. export const BookingStore = signalStore( // .. withEventHandlers((store, events =

    inject(Events), bookingService = inject(BookingService)) => ({ loadBookings$: events .on(bookingUserEvents.pageOpened) .pipe( exhaustMap(() => bookingService.loadBookings().pipe( mapResponse({ next: (bookings) => bookingApiEvents.bookingsLoadedSuccess(bookings), error: console.error, }), ), ), ), }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 withEventHandlers((store, events = inject(Events), export const BookingStore = signalStore( 1 // .. 2 3 bookingService = inject(BookingService)) => ({ 4 loadBookings$: events 5 .on(bookingUserEvents.pageOpened) 6 .pipe( 7 exhaustMap(() => 8 bookingService.loadBookings().pipe( 9 mapResponse({ 10 next: (bookings) => 11 bookingApiEvents.bookingsLoadedSuccess(bookings), 12 error: console.error, 13 }), 14 ), 15 ), 16 ), 17 }), 18 ), 19 ); 20 loadBookings$: events .on(bookingUserEvents.pageOpened) export const BookingStore = signalStore( 1 // .. 2 withEventHandlers((store, events = inject(Events), 3 bookingService = inject(BookingService)) => ({ 4 5 6 .pipe( 7 exhaustMap(() => 8 bookingService.loadBookings().pipe( 9 mapResponse({ 10 next: (bookings) => 11 bookingApiEvents.bookingsLoadedSuccess(bookings), 12 error: console.error, 13 }), 14 ), 15 ), 16 ), 17 }), 18 ), 19 ); 20
  46. export const BookingStore = signalStore( // .. withEventHandlers((store, events =

    inject(Events), bookingService = inject(BookingService)) => ({ loadBookings$: events .on(bookingUserEvents.pageOpened) .pipe( exhaustMap(() => bookingService.loadBookings().pipe( mapResponse({ next: (bookings) => bookingApiEvents.bookingsLoadedSuccess(bookings), error: console.error, }), ), ), ), }), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 withEventHandlers((store, events = inject(Events), export const BookingStore = signalStore( 1 // .. 2 3 bookingService = inject(BookingService)) => ({ 4 loadBookings$: events 5 .on(bookingUserEvents.pageOpened) 6 .pipe( 7 exhaustMap(() => 8 bookingService.loadBookings().pipe( 9 mapResponse({ 10 next: (bookings) => 11 bookingApiEvents.bookingsLoadedSuccess(bookings), 12 error: console.error, 13 }), 14 ), 15 ), 16 ), 17 }), 18 ), 19 ); 20 loadBookings$: events .on(bookingUserEvents.pageOpened) export const BookingStore = signalStore( 1 // .. 2 withEventHandlers((store, events = inject(Events), 3 bookingService = inject(BookingService)) => ({ 4 5 6 .pipe( 7 exhaustMap(() => 8 bookingService.loadBookings().pipe( 9 mapResponse({ 10 next: (bookings) => 11 bookingApiEvents.bookingsLoadedSuccess(bookings), 12 error: console.error, 13 }), 14 ), 15 ), 16 ), 17 }), 18 ), 19 ); 20 bookingService.loadBookings().pipe( mapResponse({ next: (bookings) => bookingApiEvents.bookingsLoadedSuccess(bookings), error: console.error, }), ), export const BookingStore = signalStore( 1 // .. 2 withEventHandlers((store, events = inject(Events), 3 bookingService = inject(BookingService)) => ({ 4 loadBookings$: events 5 .on(bookingUserEvents.pageOpened) 6 .pipe( 7 exhaustMap(() => 8 9 10 11 12 13 14 15 ), 16 ), 17 }), 18 ), 19 ); 20
  47. export const bookingApiEvents = eventGroup({ ... }); export const bookingUserEvents

    = eventGroup({ ... }); export const BookingStore = signalStore( withState<BookingState>(initialBookingState), withReducer( on(bookingApiEvents.bookingsLoadedSuccess, ({ payload }) => ({ bookings: payload })), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  48. export const bookingApiEvents = eventGroup({ ... }); export const bookingUserEvents

    = eventGroup({ ... }); export const BookingStore = signalStore( withState<BookingState>(initialBookingState), withReducer( on(bookingApiEvents.bookingsLoadedSuccess, ({ payload }) => ({ bookings: payload })), ), ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 withReducer( on(bookingApiEvents.bookingsLoadedSuccess, ({ payload }) => ({ bookings: payload })), ), export const bookingApiEvents = eventGroup({ 1 ... 2 }); 3 4 export const bookingUserEvents = eventGroup({ 5 ... 6 }); 7 8 export const BookingStore = signalStore( 9 withState<BookingState>(initialBookingState), 10 11 12 13 14 15 ); 16
  49. @Component({ /* ... */ }) export class BookingsComponent { readonly

    dispatcher = inject(Dispatcher); constructor() { this.dispatcher.dispatch(bookingUserEvents.pageOpened()); } } @Component({ /* ... */ }) export class BookingsComponent { readonly dispatch = injectDispatch(bookingUserEvents); constructor() { dispatch.pageOpened() } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  50. @Component({ /* ... */ }) export class BookingsComponent { readonly

    dispatcher = inject(Dispatcher); constructor() { this.dispatcher.dispatch(bookingUserEvents.pageOpened()); } } @Component({ /* ... */ }) export class BookingsComponent { readonly dispatch = injectDispatch(bookingUserEvents); constructor() { dispatch.pageOpened() } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export class BookingsComponent { readonly dispatcher = inject(Dispatcher); constructor() { this.dispatcher.dispatch(bookingUserEvents.pageOpened()); } } @Component({ /* ... */ }) 1 2 3 4 5 6 7 8 9 10 @Component({ /* ... */ }) 11 export class BookingsComponent { 12 readonly dispatch = injectDispatch(bookingUserEvents); 13 14 constructor() { 15 dispatch.pageOpened() 16 } 17 } 18
  51. @Component({ /* ... */ }) export class BookingsComponent { readonly

    dispatcher = inject(Dispatcher); constructor() { this.dispatcher.dispatch(bookingUserEvents.pageOpened()); } } @Component({ /* ... */ }) export class BookingsComponent { readonly dispatch = injectDispatch(bookingUserEvents); constructor() { dispatch.pageOpened() } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export class BookingsComponent { readonly dispatcher = inject(Dispatcher); constructor() { this.dispatcher.dispatch(bookingUserEvents.pageOpened()); } } @Component({ /* ... */ }) 1 2 3 4 5 6 7 8 9 10 @Component({ /* ... */ }) 11 export class BookingsComponent { 12 readonly dispatch = injectDispatch(bookingUserEvents); 13 14 constructor() { 15 dispatch.pageOpened() 16 } 17 } 18 export class BookingsComponent { readonly dispatch = injectDispatch(bookingUserEvents); constructor() { dispatch.pageOpened() } } @Component({ /* ... */ }) 1 export class BookingsComponent { 2 readonly dispatcher = inject(Dispatcher); 3 4 constructor() { 5 this.dispatcher.dispatch(bookingUserEvents.pageOpened()); 6 } 7 } 8 9 10 @Component({ /* ... */ }) 11 12 13 14 15 16 17 18