NgRx SignalStore is a lightweight state management solution built on Angular Signals — simpler than the classic NgRx Store while still providing structure for complex state.
npm install @ngrx/signals
// product.store.ts
import { signalStore, withState, withComputed, withMethods } from '@ngrx/signals';
import { computed, inject } from '@angular/core';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { tapResponse } from '@ngrx/operators';
import { pipe, switchMap } from 'rxjs';
interface ProductState {
products: Product[];
loading: boolean;
error: string | null;
filter: string;
}
export const ProductStore = signalStore(
{ providedIn: 'root' },
withState<ProductState>({
products: [],
loading: false,
error: null,
filter: '',
}),
withComputed(({ products, filter }) => ({
filteredProducts: computed(() =>
products().filter(p => p.name.toLowerCase().includes(filter().toLowerCase()))
),
totalCount: computed(() => products().length),
})),
withMethods((store, productService = inject(ProductService)) => ({
loadProducts: rxMethod<void>(
pipe(
tap(() => patchState(store, { loading: true })),
switchMap(() =>
productService.getAll().pipe(
tapResponse({
next: products => patchState(store, { products, loading: false }),
error: err => patchState(store, { error: String(err), loading: false }),
})
)
)
)
),
setFilter(filter: string): void {
patchState(store, { filter });
},
}))
);
@Component({ /* ... */ })
export class ProductListComponent implements OnInit {
readonly store = inject(ProductStore);
ngOnInit(): void { this.store.loadProducts(); }
}
<input [value]="store.filter()" (input)="store.setFilter($event.target.value)" />
@for (product of store.filteredProducts(); track product.id) {
<app-product-card [product]="product" />
}