From 950a8065331e9454b81173b26ebc8a0fff4c226b Mon Sep 17 00:00:00 2001 From: "J. Degand" <70610011+jdegand@users.noreply.github.com> Date: Mon, 21 Jul 2025 00:53:03 +0000 Subject: [PATCH] docs(www): add llms.txt files --- projects/www/public/llms-full.txt | 20553 ++++++++++++++++++++++++++++ projects/www/public/llms.txt | 29 + 2 files changed, 20582 insertions(+) create mode 100644 projects/www/public/llms-full.txt create mode 100644 projects/www/public/llms.txt diff --git a/projects/www/public/llms-full.txt b/projects/www/public/llms-full.txt new file mode 100644 index 0000000000..92cf4da37f --- /dev/null +++ b/projects/www/public/llms-full.txt @@ -0,0 +1,20553 @@ +# What is NgRx? + +NgRx is a framework for building reactive applications in Angular. NgRx provides libraries for: + +- Managing global and local state. +- Isolation of side effects to promote a cleaner component architecture. +- Entity collection management. +- Integration with the Angular Router. +- Developer tooling that enhances developer experience when building many different types of applications. + +## Packages + +NgRx packages are divided into a few main categories + +### State + +- [Store](guide/store) - RxJS powered global state management for Angular apps, inspired by Redux. +- [Effects](guide/effects) - Side effect model for @ngrx/store. +- [Router Store](guide/router-store) - Bindings to connect the Angular Router to @ngrx/store. +- [Entity](guide/entity) - Entity State adapter for managing record collections. +- [Signals](guide/signals) - Reactive store and set of utilities for Angular Signals. +- [ComponentStore](guide/component-store) - Standalone library for managing local/component state. +- [Operators](guide/operators) - Shared RxJS operators for NgRx libraries. + +### Data + +- [Data](guide/data) - Extension for simplified entity data management. + +### View + +- [Component](guide/component) - Extension for building reactive Angular templates. + +### Developer Tools + +- [Store Devtools](guide/store-devtools) - Instrumentation for @ngrx/store that enables visual tracking of state and time-travel debugging. +- [Schematics](guide/schematics) - Scaffolding library for Angular applications using NgRx libraries. +- [ESLint Plugin](guide/eslint-plugin) - ESLint rules to warn against bad practices. It also contains a few automatic fixes to enforce a consistent style, and to promote best practice. + + +# @ngrx/component + +Component is a library for building reactive Angular templates. +It provides a set of declarables that can work with or without `zone.js`. +They give more control over rendering and provide further reactivity for Angular applications. + +## Key Concepts + +- Rendering observable events in a performant way. +- Displaying different content based on the current state of an observable. +- Combining multiple observables in the template. +- Creating readable templates by using aliases for nested properties. +- Building fully reactive Angular applications regardless of whether `zone.js` is present or not. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/component/install) page. + +## Available Features + +Learn more about features provided by the `@ngrx/component` package through the [`*ngrxLet` directive](guide/component/let) +and [`ngrxPush` pipe](guide/component/push) docs. + + +# Component Installation + +## Installing with `ng add` + +You can install the Component package to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/component@latest +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/component`. +2. Run the package manager to install the added dependency. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/component --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/component +``` + +# Let Directive + +The `*ngrxLet` directive serves a convenient way of binding observables to a view context +(DOM element's scope). It also helps with several internal processing under the hood. + +## Usage + +The `*ngrxLet` directive is a standalone directive. +To use it, add the `LetDirective` to the `imports` of your standalone component or NgModule: + +```ts +import { Component } from '@angular/core'; +import { LetDirective } from '@ngrx/component'; + +@Component({ + // ... other metadata + imports: [ + // ... other imports + LetDirective, + ], +}) +export class MyStandaloneComponent {} +``` + +## Comparison with `*ngIf` and `async` + +The current way of binding an observable to the view looks like this: + +```html + + + + + +``` + +The problem is that `*ngIf` is interfering with rendering. +In case of `0` (falsy value), the component would be hidden. + +The `*ngrxLet` directive takes over several things and makes it more convenient +and safe to work with streams in the template: + +```html + + + + + + + +``` + +## Tracking Different Observable Events + +In addition to that it provides us information from the whole observable context. +We can track next, error, and complete events: + +```html + + + +

There is an error: {{ e }}

+

Observable is completed.

+
+``` + +## Combining Multiple Observables + +The `*ngrxLet` directive can be also used with a dictionary of observables. +This feature provides the ability to create a view model object in the template: + +```html + + + + +``` + +## Using Suspense Template + +There is an option to pass the suspense template that will be displayed +when an observable is in a suspense state: + +```html + + + + + +

Loading...

+
+``` + + + +An observable is in a suspense state until it emits the first event (next, error, or complete). + + + +In case a new observable is passed to the `*ngrxLet` directive at runtime, +the suspense template will be displayed again until the new observable emits the first event. + +## Using Aliases for Non-Observable Values + +The `*ngrxLet` directive can also accept static (non-observable) values as input argument. +This feature provides the ability to create readable templates by using aliases for deeply nested properties: + +```html + + + + +

This field is required.

+

This field must be an email.

+
+
+``` + +## Included Features + +- Binding is present even for falsy values. + (See ["Comparison with `*ngIf` and `async`"](#comparison-with-ngif-and-async) section) +- Takes away the multiple usages of the `async` or `ngrxPush` pipe. +- Allows displaying different content based on the current state of an observable. +- Allows combining multiple observables in the template. +- Provides a unified/structured way of handling `null` and `undefined`. +- Provides the ability to create readable templates by using aliases for nested properties. +- Triggers change detection using the `RenderScheduler` that behaves differently in + zone-full and zone-less mode. +- Distinct the same values in a row for better performance. + +# Push Pipe + +The `ngrxPush` pipe serves as a drop-in replacement for the `async` pipe. +It contains intelligent handling of change detection to enable us +running in zone-full as well as zone-less mode without any changes to the code. + +## Usage + +The `ngrxPush` pipe is a standalone pipe. +To use it, add the `PushPipe` to the `imports` of your standalone component or NgModule: + +```ts +import { Component } from '@angular/core'; +import { PushPipe } from '@ngrx/component'; + +@Component({ + // ... other metadata + imports: [ + // ... other imports + PushPipe, + ], +}) +export class MyStandaloneComponent {} +``` + +## Comparison with `async` Pipe + +The current way of binding an observable to the view looks like this: + +```html +

{{ number$ | async }}

+ +{{ n }} + + +``` + +The `async` pipe marks the component and all its ancestors as dirty, but does not trigger the change detection mechanism. +It needs the `zone.js` microtask queue to exhaust until `ApplicationRef.tick` is called to render all dirty marked components. +To use the `async` pipe in zone-less mode, we have to manually trigger the change detection each time an observable +emits a new value. + +Fortunately, the `ngrxPush` pipe solves this problem by scheduling a new change detection cycle in zone-less mode when +an observable emits a new value. It can be used as follows: + +```html +

{{ number$ | ngrxPush }}

+ +{{ n }} + + +``` + +## Combining Multiple Observables + +The `ngrxPush` pipe can be also used with a dictionary of observables in the +following way: + +```html + + {{ { users: users$, query: query$ } | ngrxPush | json }} + +``` + +## Included Features + +- Takes observables or promises, retrieves their values, and passes the value to the template. +- Allows combining multiple observables in the template. +- Handles `null` and `undefined` values in a clean unified/structured way. +- Triggers change detection using the `RenderScheduler` that behaves differently in + zone-full and zone-less mode. +- Distinct the same values in a row for better performance. + +# Comparison of ComponentStore and Store + +Both ComponentStore and Store are designed to manage the state in an Angular application, however +they are addressing different problems. These libraries are independent of each other, and, +depending on many factors, one or the other or even both in tandem would be recommended. + +Among the factors that would influence which of the libraries (or both) should be used are the following: + +- **Size of the application**. How many components does it have? +- **Interconnection of the app**. Are these components tied together, or are they independent + groups of components sub-trees? +- **Depth of component tree**. How many levels of depth does the component tree have? +- **State ownership**. Could there be a clear separation of state ownership at different + nodes of the components tree? +- **State lifespan**. Is the state needed throughout the lifespan of the application, or only when + some pages are displayed and we want to automatically clean it up when the user navigates somewhere + else? +- **Business Requirements**. How well are all of the business requirements understood and finalized before the + implementation of the app starts? Would it be changing frequently? +- **Lifespan of the app**. Is this a short-lived Minimum Viable Product (MVP) that would be discarded, a solution that won't need much support or changes once it's released, or is it a long-term product + that would be constantly changing, based on changing business needs? +- **Backend APIs**. Does the team have influence over backend(s) and the APIs that they provide? + +The longer the lifespan of the app and the larger it is, the greater the need to separate how the +data is retrieved and how components are displaying it. That drives earlier separation of +_"Data Transfer Objects"_ (aka _"Network Models"_) - the models used to communicate with backend(s) - and _"View Models"_ - the models +used by components in the templates. + +ComponentStore is responsible for managing smaller, more local state. While it's possible to have +multiple ComponentStores, one has to have a clear understanding of state ownership of each one of +them. + +## Benefits and Trade-offs + +ComponentStore and Global Store have many benefits, some of which are listed in the [introduction](guide/store/why#why-use-ngrx-store-for-state-management). They help organize state, make migrations to new APIs easier, +encapsulate changes and side-effects, make our components smaller, easier to test and more +performant, but they are also introducing code complexity with **indirections**. + + + +**Note:** It's important to understand what the cost is and why we are adding it. + + + +Both of them bring [push-based architecture](https://medium.com/@thomasburlesonIA/push-based-architectures-with-rxjs-81b327d7c32d), which is the first indirection. The developer can no longer +get the result of a service method call, instead they would be listening for Observable values +exposed by that service. The benefit, on the other side, is that the developer no longer has to worry what +is changing the state - all the component needs to know is that something has changed it. If the +component wants to change the state itself, it sends the message about it (either dispatches an +Action in Store, or calls ComponentStore's `updater` or `effect`). + +Actions are the second indirection. They are present in the Global Store only. There are many benefits +of this indirection, such as: + +- ability to trigger multiple effects/reducers at the same time +- greater scalability +- useful DevTools + +ComponentStore doesn't have that indirection, however it also loses the above-mentioned benefits. + +The scale of state that it works with has to be smaller, which brings another set of benefits, such as: + +- ComponentStore that is tied to the specific node in the components tree, will be automatically cleaned up when that node is destroyed +- state is fully self-contained with each ComponentStore, and thus allows to have multiple + independent instances of the same component +- provides simpler state management to small/medium sized apps + + + +The difference between the benefits and trade-offs of Stores make Global Store better suited for +managing global shared state, where ComponentStore shines managing more local, encapsulated state, +as well as component UI state. + + + +Depending on the needs of the application, the developer might choose Store or ComponentStore, or, +in cases of the larger apps, both Store **and** ComponentStore. + +## State ownership + +The Global Store works with the **single** immutable object, that contains all of the shared state throughout +the application. There are multiple reducers, each one responsible for a particular **slice** of +state. + +Each ComponentStore is fully responsible for its own state. There could be **many** different +ComponentStores, but each one should store its own distinct state. + +
+ Comparison of NgRx Store and Component Store state ownership or placement +
+ +## File structure + +ComponentStore is focused on a smaller part of the state, and thus should contain not only the state +itself, but also every "prescription" of how it could be changed. All "`updater`s" and "`effect`s" +should be part of the ComponentStore, responsible for the specific state. + +It makes ComponentStore less scalable - if there are too many updaters and effects in a single class, +then it quickly becomes unreadable. + +Shared `select`ors should also be part of the ComponentStore, however downstream components might +have their component-specific details, such as aggregating all the info needed for their _"View Model"_. +In such cases, it's acceptable to create `ComponentStore` that won't be managing state +and would contain a number of selectors. + +
+ Comparison of NgRx Store and Component Store file structures +
+ +# Effects + +Effects are designed to extract any side-effects (such as Network calls) from components and +handle potential race conditions. + +## Key Concepts + +- Effects isolate side effects from components, allowing for more pure components that select state and trigger updates and/or effects in ComponentStore(s). +- Effects are Observables listening for the inputs and piping them through the "prescription". +- Those inputs can either be values or Observables of values. +- Effects perform tasks, which are synchronous or asynchronous. + +## `effect` method + +The `effect` method takes a callback with an Observable of values, that describes HOW new +incoming values should be handled. Each new call of the effect would push the value into that +Observable. + + + +```ts +@Injectable() +export class MoviesStore extends ComponentStore { + constructor(private readonly moviesService: MoviesService) { + super({ movies: [] }); + } + + // Each new call of getMovie(id) pushed that id into movieId$ stream. + readonly getMovie = this.effect((movieId$: Observable) => { + return movieId$.pipe( + // πŸ‘‡ Handle race condition with the proper choice of the flattening operator. + switchMap((id) => + this.moviesService.fetchMovie(id).pipe( + //πŸ‘‡ Act on the result within inner pipe. + tap({ + next: (movie) => this.addMovie(movie), + error: (e) => this.logError(e), + }), + // πŸ‘‡ Handle potential error within inner pipe. + catchError(() => EMPTY) + ) + ) + ); + }); + + readonly addMovie = this.updater((state, movie: Movie) => ({ + movies: [...state.movies, movie], + })); + + selectMovie(movieId: string) { + return this.select((state) => + state.movies.find((m) => m.id === movieId) + ); + } +} +``` + + + +The `getMovie` effect could then be used within a component. + + + +```ts +@Component({ + template: `...`, + // ❗️MoviesStore is provided higher up the component tree +}) +export class MovieComponent { + movie$: Observable; + + @Input() + set movieId(value: string) { + // calls effect with value. πŸ‘‡ Notice it's a single string value. + this.moviesStore.getMovie(value); + this.movie$ = this.moviesStore.selectMovie(value); + } + + constructor(private readonly moviesStore: MoviesStore) {} +} +``` + + + +## Calling an `effect` without parameters + +A common use case is to call the `effect` method without any parameters. +To make this possible set the generic type of the `effect` method to `void`. + + + +```ts + readonly getAllMovies = this.effect( + // The name of the source stream doesn't matter: `trigger$`, `source$` or `$` are good + // names. We encourage to choose one of these and use them consistently in your codebase. + (trigger$) => trigger$.pipe( + exhaustMap(() => + this.moviesService.fetchAllMovies().pipe( + tapResponse({ + next: (movies) => this.addAllMovies(movies), + error: (error: HttpErrorResponse) => this.logError(error), + }) + ) + ) + ) + ); +``` + + + +# @ngrx/component-store + +ComponentStore is a stand-alone library that helps to manage local/component state. It's an alternative to reactive push-based "Service with a Subject" approach. + +## Key Concepts + +- Local state has to be initialized, but it can be done lazily. +- Local state is typically tied to the life-cycle of a particular component and is cleaned up when that component is destroyed. +- Users of ComponentStore can update the state through `setState` or `updater`, either imperatively or by providing an Observable. +- Users of ComponentStore can read the state through `select` or a top-level `state$`. Selectors are very performant. +- Users of ComponentStore may start side-effects with `effect`, both sync and async, and feed the data both imperatively or reactively. + +The details about [initialization](guide/component-store/initialization), [writing](guide/component-store/write) and [reading](guide/component-store/read) from state, +and [side-effects](guide/component-store/effect) management can be found in the corresponding sections of the Architecture. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/component-store/install) page. + +## @ngrx/store or @ngrx/component-store? + +The Global Store and Component Store are designed to solve different problems and can be used independently from each other. The detailed comparison can +be found at [Store vs ComponentStore](guide/component-store/comparison) section. + +# Initialization + +ComponentStore can be initialized in 2 ways: + +- through the constructor - it would have the initial state +- by calling `setState` and passing an object that matches the state interface. + +## Initialization through the constructor + +Initializing through the constructor makes the state immediately available to the ComponentStore consumers. + + + +```ts +export interface MoviesState { + movies: Movie[]; +} + +@Injectable() +export class MoviesStore extends ComponentStore { + constructor() { + super({ movies: [] }); + } +} +``` + + + +## Lazy initialization + +In some cases, developers do not want selectors to return any state until there's meaningful data in the ComponentStore. The solution +would be to initialize the state lazily by calling [`setState`](guide/component-store/write#setstate-method) and passing the full state to it. The same approach can be taken to reset the state. + + + +**Note:** Initialization has to be done before updating the state, otherwise an error would be thrown. + + + + + +```ts +@Component({ + template: ` +
  • + {{ movie.name }} +
  • + `, + providers: [ComponentStore], +}) +export class MoviesPageComponent { + readonly movies$ = this.componentStore.select( + (state) => state.movies + ); + + constructor( + private readonly componentStore: ComponentStore<{ + movies: Movie[]; + }> + ) {} + + ngOnInit() { + this.componentStore.setState({ movies: [] }); + } +} +``` + +
    + + +# Component Store Installation + +## Installing with `ng add` + +You can install the ComponentStore to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/component-store@latest +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/component-store`. +2. Run the package manager to install the added dependency. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/component-store --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/component-store +``` + + +# Lifecycle + +NgRx ComponentStore comes with lifecycle hooks and observables for performing tasks after the ComponentStore is initially instantiated, after the initial state is first set, and when the ComponentStore is destroyed. You can use these lifecycle hooks to set up long-running effects, wire up additional logic, and other tasks outside the constructor of the ComponentStore. + +## Setup + +Both lifecycle hooks are enabled by providing the ComponentStore through the `provideComponentStore()` function. This function registers the ComponentStore as a provider, sets up a factory provider to instantiate the ComponentStore instance, and calls the implemented lifecycle hooks. + +Currently, Angular provides initializer tokens in a few areas. The `APP_INITIALIZER` and `BOOTSTRAP_INITIALIZER` for application/bootstrap init logic, and the `ENVIRONMENT_INITIALIZER` for environment injector init logic. The `provideComponentStore()` mimics this behavior to run the lifecycle hooks. The function is required because there aren't any provided tokens at the component level injector to allow initialization tasks. + + + +**Note:** If you implement the lifecycle hooks in the ComponentStore, and register it with `providers` without using `provideComponentStore()`, in development mode, a warning is logged to the browser console. + + + +## OnStoreInit + +The `OnStoreInit` interface is used to implement the `ngrxOnStoreInit` method in the ComponentStore class. This lifecycle method is called immediately after the ComponentStore class is instantiated. + + + +```ts +export interface BooksState { + collection: Book[]; +} + +export const initialState: BooksState = { + collection: [], +}; + +@Injectable() +export class BooksStore + extends ComponentStore + implements OnStoreInit +{ + constructor() { + super(initialState); + } + + ngrxOnStoreInit() { + // called after store has been instantiated + } +} +``` + + + + + +```ts +@Component({ + // ... other metadata + providers: [provideComponentStore(BooksStore)], +}) +export class BooksPageComponent { + constructor(private booksStore: BooksStore) {} +} +``` + + + +## OnStateInit + +The `OnStateInit` interface is used to implement the `ngrxOnStateInit` method in the ComponentStore class. This lifecycle method is called **only once** after the ComponentStore state is initially set. ComponentStore supports eager and lazy initialization of state, and the lifecycle hook is called appropriately in either scenario. + +### Eager State Init + + + +```ts +export interface BooksState { + collection: Book[]; +} + +export const initialState: BooksState = { + collection: [], +}; + +@Injectable() +export class BooksStore + extends ComponentStore + implements OnStateInit +{ + constructor() { + // eager state initialization + super(initialState); + } + + ngrxOnStateInit() { + // called once after state has been first initialized + } +} +``` + + + + + +```ts +@Component({ + // ... other metadata + providers: [provideComponentStore(BooksStore)], +}) +export class BooksPageComponent { + constructor(private booksStore: BooksStore) {} +} +``` + + + +### Lazy State Init + + + +```ts +export interface BooksState { + collection: Book[]; +} + +@Injectable() +export class BooksStore + extends ComponentStore + implements OnStateInit +{ + constructor() { + super(); + } + + ngrxOnStateInit() { + // called once after state has been first initialized + } +} + +export const initialState: BooksState = { + collection: [], +}; +``` + + + + + +```ts +@Component({ + // ... other metadata + providers: [provideComponentStore(BooksStore)], +}) +export class BooksPageComponent implements OnInit { + constructor(private booksStore: BooksStore) {} + + ngOnInit() { + // lazy state initialization + this.booksStore.setState(initialState); + } +} +``` + + + +## OnDestroy + +ComponentStore also implements the `OnDestroy` interface from `@angular/core` to complete any internally created observables. + +It also exposes a `destroy$` property on the ComponentStore class that can be used instead of manually creating a `Subject` to unsubscribe from any observables created in the component. + + + +**Note:** If you override the `ngOnDestroy` method in your component store, you need to call `super.ngOnDestroy()`. Otherwise a memory leak may occur. + + + + + +```ts +@Injectable() +export class MoviesStore + extends ComponentStore + implements OnDestroy +{ + constructor() { + super({ movies: [] }); + } + + override ngOnDestroy(): void { + // πŸ‘‡ add this line + super.ngOnDestroy(); + } +} +``` + + + + + +```ts +@Component({ + // ... other metadata + providers: [ComponentStore], +}) +export class BooksPageComponent implements OnInit { + constructor(private cs: ComponentStore) {} + + ngOnInit() { + const timer = interval(1000) + .pipe(takeUntil(this.cs.destroy$)) + .subscribe(() => { + // listen until ComponentStore is destroyed + }); + } +} +``` + + + +The `provideComponentStore()` function is not required to listen to the `destroy$` property on the ComponentStore. + + +# Reading state + +## `select` method + +Reading state is done with the `select` method, which takes a projector that describes HOW the state is retrieved and/or transformed. +Selectors emit new values when those values "change" - the new value is no longer distinct by comparison from the previous value. + +Another performance benefit of selectors is that they are "shared" - they multicast the value to each subscriber. + + + +```ts +export interface MoviesState { + movies: Movie[]; +} + +@Injectable() +export class MoviesStore extends ComponentStore { + constructor() { + super({ movies: [] }); + } + + readonly movies$: Observable = this.select( + (state) => state.movies + ); +} +``` + + + + + +```ts +@Component({ + template: ` +
  • + {{ movie.name }} +
  • + `, + providers: [MoviesStore], +}) +export class MoviesPageComponent { + movies$ = this.moviesStore.movies$; + + constructor(private readonly moviesStore: MoviesStore) {} +} +``` + +
    + +## Combining selectors + +Selectors can be used to combine other Selectors or Observables. + + + +```ts +export interface MoviesState { + movies: Movie[]; + userPreferredMoviesIds: string[]; +} + +@Injectable() +export class MoviesStore extends ComponentStore { + constructor() { + super({ movies: [], userPreferredMoviesIds: [] }); + } + + readonly movies$ = this.select((state) => state.movies); + readonly userPreferredMovieIds$ = this.select( + (state) => state.userPreferredMoviesIds + ); + + readonly userPreferredMovies$ = this.select( + this.movies$, + this.userPreferredMovieIds$, + (movies, ids) => movies.filter((movie) => ids.includes(movie.id)) + ); +} +``` + + + +## Creating a View Model + +Creating view models is a recommended way to consolidate multiple streams in a clean API to provide to your component template. +The `select` method accepts a dictionary of observables as input and returns an observable of the dictionary of values as output. View models can be written in the following way: + + + +```ts + private readonly vm$ = this.select({ + movies: this.movies$, + userPreferredMovieIds: this.userPreferredMovieIds$, + userPreferredMovies: this.userPreferredMovies$ + }); +``` + + + +## Debounce selectors + +Selectors are synchronous by default, meaning that they emit the value immediately when subscribed to, and on every state change. +Sometimes the preferred behavior would be to wait (or debounce) until the state "settles" (meaning all the changes within the current microtask occur) +and only then emit the final value. +In many cases, this would be the most performant way to read data from the ComponentStore, however its behavior might be surprising sometimes, as it won't emit a value until later on. +This makes it harder to test such selectors. + +Adding the debounce to a selector is done by passing `{debounce: true}` as the last argument. + + + +```ts +@Injectable() +export class MoviesStore extends ComponentStore { + constructor(private movieService: MovieService) { + super({ movies: [], moviesPerPage: 10, currentPageIndex: 0 }); + + // πŸ‘‡ effect is triggered whenever debounced data is changed + this.fetchMovies(this.fetchMoviesData$); + } + + // Updates how many movies per page should be displayed + readonly updateMoviesPerPage = this.updater( + (state, moviesPerPage: number) => ({ + ...state, + moviesPerPage, // updates with new value + }) + ); + + // Updates which page of movies that the user is currently on + readonly updateCurrentPageIndex = this.updater( + (state, currentPageIndex: number) => ({ + ...state, + currentPageIndex, // updates with new page index + }) + ); + + readonly moviesPerPage$ = this.select( + (state) => state.moviesPerPage + ); + + readonly currentPageIndex$ = this.select( + (state) => state.currentPageIndex + ); + + private readonly fetchMoviesData$ = this.select( + { + moviesPerPage: this.moviesPerPage$, + currentPageIndex: this.currentPageIndex$, + }, + { debounce: true } // πŸ‘ˆ setting this selector to debounce + ); + + private readonly fetchMovies = this.effect( + ( + moviePageData$: Observable<{ + moviesPerPage: number; + currentPageIndex: number; + }> + ) => { + return moviePageData$.pipe( + concatMap(({ moviesPerPage, currentPageIndex }) => { + return this.movieService + .loadMovies(moviesPerPage, currentPageIndex) + .pipe(tap((results) => this.updateMovieResults(results))); + }) + ); + } + ); +} +``` + + + +## Using a custom equality function + +The observable created by the `select` method compares the newly emitted value with the previous one using the default equality check (`===`) and emits only if the value has changed. However, the default behavior can be overridden by passing a custom equality function to the `select` method config. + + + +```ts +export interface MoviesState { + movies: Movie[]; +} + +@Injectable() +export class MoviesStore extends ComponentStore { + constructor() { + super({ movies: [] }); + } + + readonly movies$: Observable = this.select( + (state) => state.movies, + { equal: (prev, curr) => prev.length === curr.length } // πŸ‘ˆ custom equality function + ); +} +``` + + + +## Selecting from global `@ngrx/store` + +ComponentStore is an independent library, however it can easily consume data from `@ngrx/store` or from any other global state management library. + + + +```ts +private readonly fetchMoviesData$ = this.select( + this.store.select(getUserId), // πŸ‘ˆ store.select returns an Observable, which is easily mixed within selector + moviesPerPage$, + currentPageIndex$, + (userId, moviesPerPage, currentPageIndex) => ({userId, moviesPerPage, currentPageIndex}), +); +``` + + + +## `selectSignal` method + +ComponentStore also provides the `selectSignal` method, which has two signatures. +The first signature creates a signal from the provided state projector function. +The second signature creates a signal by combining the provided signals, this is similar to the `select` method that combines the provided observables. + + + +```ts +import { Injectable } from '@angular/core'; +import { ComponentStore } from '@ngrx/component-store'; + +import { User } from './user.model'; + +type UsersState = { users: User[]; query: string }; + +@Injectable() +export class UsersStore extends ComponentStore { + // type: Signal + readonly users = this.selectSignal((s) => s.users); + // type: Signal + readonly query = this.selectSignal((s) => s.query); + // type: Signal + readonly filteredUsers = this.selectSignal( + this.users, + this.query, + (users, query) => users.filter(({ name }) => name.includes(query)) + ); +} +``` + + + +The `selectSignal` method also accepts an equality function to stop the recomputation of the deeper dependency chain if two values are determined to be equal. + +## `state` signal + +The `state` signal returns the entire state of the ComponentStore. + +Use the `state` signal to create computed signals that derives its value from the state. + + + +```ts +import { computed, Injectable } from '@angular/core'; +import { ComponentStore } from '@ngrx/component-store'; + +import { User } from './user.model'; + +type UsersState = { users: User[]; query: string }; + +@Injectable() +export class UsersStore extends ComponentStore { + readonly users = computed(() => this.state().users); + readonly query = computed(() => this.state().query); + + readonly filteredUsers = computed(() => + this.users().filter(({ name }) => name.includes(this.query())) + ); +} +``` + + + +## `get` method + +While a selector provides a reactive way to read the state from ComponentStore via Observable, sometimes an imperative read is needed. +One of such use cases is accessing the state within an `effect`s and that's where `get` method could be used. + + + +The `get` method is ComponentStore-private, meaning it's accessible only within the ComponentStore. It's done to discourage frequent imperative reads +from the state as the NgRx team is in a consensus that such reads promote further potentially harmful architectural decisions. + + + +# Usage + +## Types of State + +There are multiple types of state that exist in the application, and state management libraries are there to help manage/synchronize/update them. The topic of which one to choose, ComponentStore or the Global Store, or maybe both would be helpful, is described at [ComponentStore vs Store](guide/component-store/comparison) section. + +The types of state that developers typically deal with in applications are: + +- **Server/Backend(s) State**. This is the ultimate source of truth of all the data. +- **Persisted State**. The "snapshots" of backend data transferred _to_ and _from_ application. E.g. Movies data passed as a JSON response, or user's rating for a particular Movie passed as an update request. +- **URL State**. This is the state of the URL itself. Depending on which URL the user navigates to, the app would open specific pages and thus might request for _Persisted State_. +- **Client State**. The state within the application that is not persisted to the backend. E.g. The info about which Tab is open in the application. +- **Local UI State**. The state within a component itself. E.g. `isEnabled` toggle state of Toggle Component. + +
    + Types of state in a typical SPA +
    + +There are more types of states, but these are the most important ones in the context of state management. + + + +Synchronizing these states is one of the most complex tasks that developers have to solve. + + + +Here is a small example to demonstrate how even a simple task might involve all of them: + +1. The user opens the page at a specific URL, "https://www.TheBestMoviesOfAllTimeEver.com/favorites". That changes the **_URL State_**. +1. The URL has a path for a specific tab, "favorites". That selection becomes part of the **_Client State_**. +1. This results in API calls to the backend to get the data of the movies that the user marked as "favorites". We receive a snapshot of **_Persisted State_**. +1. The Toggle Component that lives next to the _"is favorite"_ label is turned ON. The "ON" state is derived from the data that the application received and passed to the Toggle Component through `@Input() isEnabled: boolean`. The component itself is not aware of _Persisted State_ or what it even means to be ON in the context of the rest of the application. All it knows is that it needs to be visually displayed as ON. The `isEnabled` state is **_Local UI State_**. +1. The user might decide that this movie is no longer their favorite and would click the Toggle button to turn it OFF. The _Local UI State_ of the component is then changed, the `@Output() changed` event is emitted and picked up by a container component which would then call the backend to update the _Persisted State_. + +This was a very simple example. Typically developers have to solve many problems that are more interconnected. What if the user is not logged in? Should we wait until the new favorite state is persisted to the backend and only then show disabled state or should we do this optimistically? and so on. + + + +Understanding these types of state helps us define our usage of ComponentStore. + + + +## Use Case 1: Local UI State + +### Example 1: ComponentStore as part of the component + +The simplest example usage of `ComponentStore` is **reactive _Local UI State_**. + +
    +
    A note about component reactivity
    + +One of the ways to improve the performance of the application is to use the `OnPush` change detection strategy. However, contrary to the popular belief, we do not always need to tell Angular's change detection to `markForCheck()` or `detectChanges()` (or the Angular Ivy alternatives). As pointed out in [this article on change detection](https://indepth.dev/the-last-guide-for-angular-change-detection-youll-ever-need/), if the event originates from the component itself, the component will be dirty checked. +This means that common presentational (aka dumb) components that interact with the rest of the application with Input(s)/Output(s) do not have to be overcomplicated with reactive state, even though we did it to the Toggle Component mentioned above. + +
    + +Having said that, in most cases making _Local UI State_ **reactive** is beneficial: + +- For Zoneless application, the `async` pipe can easily be substituted with a Zoneless alternative such as the [`ngrxPush` pipe](guide/component/push) +- For components with non-trivial business logic, reactivity can organize the state better by clearly separating actual state from derived values and identifying side-effects. + + + +ComponentStore is not the only reactive _Local UI State_ holder - sometimes `FormControl`s are good enough. They contain the state and they have reactive APIs. + + + +Here's the simplified `SlideToggleComponent` example which uses `ComponentStore` for _Local UI State_. In this example, the `ComponentStore` is provided directly by the component itself, which might not be the best choice for most of the use cases of `ComponentStore`. Instead, consider a service that `extends ComponentStore`. + + + +You can see the full example at StackBlitz: + + + + + + + + + + +Below are the steps of integrating `ComponentStore` into a component. + +#### Step 1. Setting up + +First, the state for the component needs to be identified. In `SlideToggleComponent` only the state of whether the toggle is turned ON or OFF is stored. + + + +`ts` + + + +Then we need to provide `ComponentStore` in the component's providers, so that each new instance of `SlideToggleComponent` has its own `ComponentStore`. It also has to be injected into the constructor. + + + +In this example `ComponentStore` is provided directly in the component. This works for simple cases, however in real-world cases it is recommended to create a separate Service, for example `SlideToggleStore`, that would extend `ComponentStore` and would contain all the business logic. This new Service is then provided in the component. See examples below. + + + + + +`ts` + + + +Next, the default state for the component needs to be set. It could be done lazily, however it needs to be done before any of `updater`s are executed, because they rely on the state to be present and would throw an error if the state is not initialized by the time they are invoked. + +State is initialized by calling `setState` and passing an object that matches the interface of `SlideToggleState`. + + + +`setState` could be called with either an object or a callback. + +When it is called with an object, state is initialized or reset to that object. + +When it is called with a callback, the state is updated. + + + + + +`ts` + + + +#### Step 2. Updating state + +In the slide-toggle example, the state is updated either through `@Input` or by a user interaction, which results in a `onChangeEvent($event)` call in the template. Both of them change the same piece of state - `checked: boolean`, thus we have the `setChecked` updater that is reused in two places. This updater describes **HOW** the state changes - it takes the current state and a value and returns the new state. + +`@Input` here is a setter function that passes the value to the `setChecked` updater. + +When a user clicks the toggle (triggering a 'change' event), instead of calling the same updater directly, the `onChangeEvent` effect is called. This is done because we also need to have the side-effect of `event.source.stopPropagation` to prevent this event from bubbling up (slide-toggle output event in named 'change' as well) and only after that the `setChecked` updater is called with the value of the input element. + + + +`ts` + + + +#### Step 3. Reading the state + +Finally, the state is aggregated with selectors into two properties: + +- `vm$` property collects all the data needed for the template - this is the _ViewModel_ of `SlideToggleComponent`. +- `change` is the `@Output` of `SlideToggleComponent`. Instead of creating an `EventEmitter`, here the output is connected to the Observable source directly. + + + +`ts` + + + +This example does not have a lot of business logic, however it is still fully reactive. + +### Example 2: Service extending ComponentStore + +`SlideToggleComponent` is a fairly simple component and having `ComponentStore` within the component itself is still manageable. When components takes more Inputs and/or has more events within its template, it becomes larger and harder to read/maintain. + +Extracting the business logic of a component into a separate Service also helps reduce the cognitive load while reading the components code. + + + +A Service that extends ComponentStore and contains business logic of the component brings many advantages. **This is also the recommended usage of ComponentStore**. + + + +`ComponentStore` was designed with such an approach in mind. The main APIs of `ComponentStore` (`updater`, `select` and `effect`) are meant to wrap the **HOW** state is changed, extracted or effected, and then called with arguments. + +Below are the two examples of a re-implemented [Paginator component](https://material.angular.io/components/paginator/overview) from Angular Material (a UI component library). These re-implementations are very functionally close alternatives. + +Here's the source code of the [Material's paginator.ts](https://github.com/angular/components/blob/23d3c216c65b327e0acfb48b53302b8fda326e7f/src/material/paginator/paginator.ts#L112) as a reference. + +What we can see is that while the _"PaginatorComponent providing ComponentStore"_ example already makes the component a lot smaller, reactive, removes `this._changeDetectorRef.markForCheck()` and organizes it into distinct "read"/"write"/"effect" buckets, it still could be hard to read. The _"PaginatorComponent with PaginatorStore"_ example adds readability and further improves the testability of behaviors and business logic. + + + +You can see the examples at StackBlitz: + +- "PaginatorComponent providing ComponentStore" +- "PaginatorComponent with PaginatorStore Service" + + + + + + + + + + + + +#### Updating the state + +With `ComponentStore` extracted into `PaginatorStore`, the developer is now using updaters and effects to update the state. `@Input` values are passed directly into `updater`s as their arguments. + + + +`ts` + + + +Not all `updater`s have to be called in the `@Input`. For example, `changePageSize` is called from the template. + +Effects are used to perform additional validation and get extra information from sources with derived data (i.e. selectors). + + + +`ts` + + + +#### Reading the state + +`PaginatorStore` exposes the two properties: `vm$` for an aggregated _ViewModel_ to be used in the template and `page$` that would emit whenever data aggregated from a `PageEvent` changes. + + + + + + + + + + +`page$` would emit `PageEvent` when page size or page index changes, however in some cases updater would update both of these properties at the same time. To avoid "intermediary" emissions, `page$` selector is using **`{debounce: true}`** configuration. More about debouncing can be found in [selector section](guide/component-store/read#debounce-selectors). + + + +### Local UI State patterns + +Components that use `ComponentStore` for managing **Local UI State** are frequently calling `updater`s directly. + +Effects can also be used when: + +- side effects are required (e.g. `event.stopPropagation()`) +- derived data (from selectors) is needed to influence the new state +- they are orchestrating a number of well-defined updaters + +The last point can sometimes be refactored into another `updater`. Use your best judgment. + +`@Output()`s and derived data are **reacting** to these state changes and are generated using `selector`s. + +A _ViewModel_ for the component is also composed from `selector`s. + + +# Updating state + +ComponentStore can be updated in 3 ways: + +- by calling `setState`. +- by calling `patchState`. +- by creating an `updater` and passing inputs through it. + +## `updater` method + +The `updater` method describes HOW the state changes. It takes a pure function with the current state and the value as arguments, +and should return the new state, updated immutably. + +There could be many updaters within a ComponentStore. They are analogous to "CASE" statements or `on()` functions in `@ngrx/store` reducer. + + + +Using the `updater` method allows developers to extract business logic out of components into services, +which makes components easier to read and test. + + + + + +```ts +@Injectable() +export class MoviesStore extends ComponentStore { + constructor() { + super({ movies: [] }); + } + + readonly addMovie = this.updater((state, movie: Movie) => ({ + movies: [...state.movies, movie], + })); +} +``` + + + +Updater then can be called with the values imperatively or could take an Observable. + + + +```ts +@Component({ + template: ` + + `, + providers: [MoviesStore], +}) +export class MoviesPageComponent { + constructor(private readonly moviesStore: MoviesStore) {} + + add(movie: string) { + this.moviesStore.addMovie({ name: movie, id: generateId() }); + } +} +``` + + + +## `setState` method + +The `setState` method can be called by either providing the object of state type or as a callback. + +When the object is provided it resets the entire state to the provided value. This is also how lazy +initialization is performed. + +The callback approach allows developers to change the state partially. + + + +```ts +@Component({ + template: `...`, + providers: [ComponentStore], +}) +export class MoviesPageComponent implements OnInit { + constructor( + private readonly componentStore: ComponentStore + ) {} + + ngOnInit() { + this.componentStore.setState({ movies: [] }); + } + + resetMovies() { + // resets the State to empty array πŸ‘‡ + this.componentStore.setState({ movies: [] }); + } + + addMovie(movie: Movie) { + this.componentStore.setState((state) => { + return { + ...state, + movies: [...state.movies, movie], + }; + }); + } +} +``` + + + +## `patchState` method + +The `patchState` method can be called by providing a partial state Observable, object, or a partial updater callback. + +When the partial state is provided it patches the state with the provided value. + +When the partial updater is provided it patches the state with the value returned from the callback. + + + +**Note:** The state has to be initialized before any of `patchState` calls, otherwise "not initialized" error will be thrown. + + + + + +```ts +interface MoviesState { + movies: Movie[]; + selectedMovieId: string | null; +} + +@Component({ + template: `...`, + providers: [ComponentStore], +}) +export class MoviesPageComponent implements OnInit { + constructor( + private readonly componentStore: ComponentStore + ) {} + + ngOnInit() { + this.componentStore.setState({ + movies: [], + selectedMovieId: null, + }); + } + + updateSelectedMovie(selectedMovieId: string) { + this.componentStore.patchState({ selectedMovieId }); + } + + addMovie(movie: Movie) { + this.componentStore.patchState((state) => ({ + movies: [...state.movies, movie], + })); + } +} +``` + + + + +# Architecture overview + +You describe your entity model to NgRx Data in a few lines of [entity metadata](guide/data/entity-metadata) and let the library do the rest of the work. + +Your component injects an NgRx Data **`EntityCollectionService`** and calls one or more of the standard set of command methods for dispatching actions. + +Your component also subscribes to one or more of the service's `Observable` _selectors_ in order to reactively process and display entity state changes produced by those commands. + +NgRx Data is really just NgRx under the hood. The data flows in typical NgRx fashion. +The following diagram illustrates the journey of a persistence `EntityAction` +such as `QUERY_ALL` for the `Hero` entity type. + +
    + flow diagram +
    + +1. The view/component calls [`EntityCollectionService.getAll()`](guide/data/entity-services), which dispatches the hero's `QUERY_ALL` [EntityAction](guide/data/entity-actions) to the store. + +2. NgRx kicks into gear ... + + 1. The NgRx Data [EntityReducer](guide/data/entity-reducer) reads the action's `entityName` property (`Hero` in this example) and + forwards the action and existing entity collection state to the `EntityCollectionReducer` for heroes. + + 1. The collection reducer picks a switch-case based on the action's `entityOp` (operation) property. + That case processes the action and collection state into a new (updated) hero collection. + + 1. The store updates the _entity cache_ in the state tree with that updated collection. + + 1. _NgRx_ observable selectors detect and report the changes (if any) to subscribers in the view. + +3. The original `EntityAction` then goes to the [EntityEffects](guide/data/entity-effects). + +4. The effect selects an [EntityDataService](guide/data/entity-dataservice) for that entity type. The data service sends an HTTP request to the server. + +5. The effect turns the HTTP response into a new _success_ action with heroes (or an _error_ action if the request failed). + +6. _NgRx effects_ Dispatches that action to the store, which reiterates step #2 to update the collection with heroes and refresh the view. + + +# Entity Actions + +The [`EntityCollectionService`](guide/data/entity-services) dispatches an `EntityAction` to the _NgRx store_ when you call one of its commands to query or update entities in a cached collection. + +## _Action_ and _EntityAction_ + +A vanilla +[_NgRx `Action`_](guide/store/actions) is a message. +The message describes an operation that can change state in the _store_. + +The _action_'s `type` identifies the operation. +It's optional `payload` carries the message data necessary to perform the operation. + +An `EntityAction` is a super-set of the _NgRx `Action`_. +It has additional properties that guide NgRx Data's handling of the action. Here's the full interface. + + + +```ts +export interface EntityAction

    extends Action { + readonly type: string; + readonly payload: EntityActionPayload

    ; +} +``` + + + + + +```ts +export interface EntityActionPayload

    + extends EntityActionOptions { + readonly entityName: string; + readonly entityOp: EntityOp; + readonly data?: P; + + // EntityActionOptions (also an interface) + readonly correlationId?: any; + readonly isOptimistic?: boolean; + readonly mergeStrategy?: MergeStrategy; + readonly tag?: string; + error?: Error; + skip?: boolean; +} +``` + + + +- `type` - action name, typically generated from the `tag` and the `entityOp`. +- `entityName` - the name of the entity type. +- `entityOp` - the name of an entity operation. +- `data?` - the message data for the action. +- `correlationId?` - a serializable object (typically a string) for correlating related actions. +- `isOptimistic?` - true if should perform the action optimistically (before the server responds). +- `mergeStrategy` - how to merge an entity into the cache. See [Change Tracking](guide/data/entity-change-tracker). +- `tag?` - the tag to use within the generated type. If not specified, the `entityName` is the tag. +- `error?` - an unexpected action processing error. +- `skip?` - true if downstream consumers should skip processing the action. + +The `type` is the only property required by _NgRx_. It is a string that uniquely identifies the action among the set of all the types of actions that can be dispatched to the store. + +NgRx Data doesn't care about the `type`. It pays attention to the `entityName` and `entityOp` properties. + +The `entityName` is the name of the entity type. +It identifies the _entity collection_ in the NgRx Data cache to which this action applies. +This name corresponds to [NgRx Data _metadata_](guide/data/entity-metadata) for that collection. +An entity interface or class name, such as `'Hero'`, is a typical `entityName`. + +The `entityOp` identifies the operation to perform on the _entity collection_, +one of the `EntityOp` enumerations that correspond to one of the +almost _sixty_ distinct operations that NgRx Data can perform on a collection. + +The `data` is conceptually the body of the message. +Its type and content should fit the requirements of the operation to be performed. + +The optional `correlationId?` is an optional serializable object (usually a GUID) that correlates two or more actions such as the action that initiates a server action ("get all heroes") and the subsequent actions that follow after the server action completed ("got heroes successfully" or "error while getting heroes"). + +The optional `mergeStrategy` tells NgRx Data how to "merge" the result of the action into the cache. +Mostly this is an instruction to the the [Change Tracking](guide/data/entity-change-tracker) sub-system. + +The optional `tag` appears in the generated `type` text when the `EntityActionFactory` creates this `EntityAction`. + +The `entityName` is the default tag that appears between brackets in the formatted `type`, +e.g., `'[Hero] NgRx Data/query-all'`. +You can set this tag to identify the purpose of the operation and "who" dispatched it. +NgRx Data will put your tag between the brackets in the formatted `type`. + +The `error` property indicates that something went wrong while processing the action. [See more below](#action-error). + +The `skip` property tells downstream action receivers that they should skip the usual action processing. +This flag is usually missing and is implicitly false. +[See more below](#action-skip). + +## _EntityAction_ consumers + +The NgRx Data library ignores the `Action.type`. +All NgRx Data library behaviors are determined by the `entityName` and `entityOp` properties alone. + +The NgRx Data `EntityReducer` redirects an action to an `EntityCollectionReducer` based on the `entityName` +and that reducer processes the action based on the `entityOp`. + +`EntityEffects` intercepts an action if its `entityOp` is among the small set of persistence `EntityAction.entityOp` names. +The effect picks the right _data service_ for that action's `entityName`, then tells the service to make the appropriate HTTP request and handle the response. + +## Creating an _EntityAction_ + +You can create an `EntityAction` by hand if you wish. +The NgRx Data library considers _any action_ with an `entityName` and `entityOp` properties to be an `EntityAction`. + +The `EntityActionFactory.create()` method helps you create a consistently well-formed `EntityAction` instance +whose `type` is a string composed from the `tag` (the `entityName` by default) and the `entityOp`. + +For example, the default generated `Action.type` for the operation that queries the server for all heroes is `'[Hero] NgRx Data/query-all'`. + + + +The `EntityActionFactory.create()` method calls the factory's `formatActionType()` method +to produce the `Action.type` string. + +Because NgRx Data ignores the `type`, you can replace `formatActionType()` with your own method if you prefer a different format or provide and inject your own `EntityActionFactory`. + + + +Note that **_each entity type has its own \_unique_ `Action` for each operation\_**, as if you had created them individually by hand. + +## Tagging the EntityAction + +A well-formed action `type` can tell the reader what changed and +who changed it. + +The NgRx Data library doesn't look at the type of an `EntityAction`, +only its `entityName` and `entityOp`. +So you can get the same behavior from several different actions, +each with its own informative `type`, as long as they share the same +`entityName` and `entityOp`. + +The optional `tag` parameter of the `EntityActionFactory.create()` method makes +it easy to produce meaningful _EntityActions_. + +You don't have to specify a tag. The `entityName` is the default tag that appears between brackets in the formatted `type`, +e.g., `'[Hero] NgRx Data/query-all'`. + +Here's an example that uses the injectable `EntityActionFactory` to construct the default "query all heroes" action. + +```typescript +const action = this.entityActionFactory.create( + 'Hero', + EntityOp.QUERY_ALL +); + +store.dispatch(action); +``` + +Thanks to the NgRx Data _Effects_, this produces _two_ actions in the log, the first to initiate the request and the second with the successful response: + +```typescript +[Hero] ngrx/data/query-all +[Hero] ngrx/data/query-all/success +``` + +This default `entityName` tag identifies the action's target entity collection. +But you can't understand the _context_ of the action from these log entries. You don't know who dispatched the action or why. +The action `type` is too generic. + +You can create a more informative action by providing a tag that +better describes what is happening and also make it easier to find +where that action is dispatched by your code. + +For example, + +```typescript +const action = this.entityActionFactory.create( + 'Hero', + EntityOp.QUERY_ALL, + null, + { tag: 'Load Heroes On Start' } +); + +store.dispatch(action); +``` + +The action log now looks like this: + +```typescript +[Load Heroes On Start] ngrx/data/query-all +[Load Heroes On Start] ngrx/data/query-all/success +``` + +### Handcrafted _EntityAction_ + +You don't have to create entity actions with the `EntityActionFactory`. +Any action object with an `entityName` and `entityOp` property is +an entity action, as explained [below](#where-are-the-entityactions). + +The following example creates the initiating "query all heroes" action by hand. + +```typescript +const action = { + type: 'some/arbitrary/action/type', + entityName: 'Hero', + entityOp: EntityOp.QUERY_ALL, +}; + +store.dispatch(action); +``` + +It triggers the HTTP request via _NgRx Data effects_, as in the previous examples. + +Just be aware that _NgRx Data effects_ uses the `EntityActionFactory` to create the second, success Action. +Without the `tag` property, it produces a generic success action. + +The log of the two action types will look like this: + +```sh +some/arbitrary/action/type +[Hero] NgRx Data/query-all-success +``` + +## Where are the _EntityActions_? + +In an NgRx Data app, the NgRx Data library creates and dispatches _EntityActions_ for you. + +_EntityActions_ are largely invisible when you call the [`EntityCollectionService`](guide/data/entity-services) API. +You can see them in action with the +[NgRx store dev-tools](guide/store-devtools). + +## Why this matters + +In an ordinary _NgRx_ application, you hand-code every `Action` for every _state_ in the store +as well as the [reducers](guide/store/reducers) +that process those _actions_. + +It takes many _actions_, a complex _reducer_, and the help of an NgRx [Effect](guide/effects) to manage queries and saves for a _single_ entity type. + +The NgRx [Entity](guide/entity) library makes the job considerably easier. + + + +The NgRx Data library internally delegates much of the heavy lifting to NgRx _Entity_. + + + +But you must still write a lot of code for each entity type. +You're expected to create _eight actions_ per entity type and +write a _reducer_ that responds to these eight actions by calling eight methods of an NgRx [_EntityAdapter_](guide/entity/adapter#adapter-collection-methods). + +These artifacts only address the _cached_ entity collection. + +You may write as many as _eighteen additional actions_ to support a typical complement of asynchronous CRUD (Create, Retrieve, Update, Delete) operations. You'll have to dispatch them to the store where you'll process them with more _reducer_ methods and _effects_ that you must also hand code. + +With vanilla _NgRx_, you'll go through this exercise for _every entity type_. +That's a lot of code to write, test, and maintain. + +With the help of NgRx Data, you don't write any of it. +NgRx Data creates the _actions_ and the _dispatchers_, _reducers_, and _effects_ that respond to those actions. + + + +## _EntityAction.error_ + +The presence of an `EntityAction.error` property indicates that something bad happened while processing the action. + +An `EntityAction` should be immutable. The `EntityAction.error` property is the _only_ exception and is strictly an internal property of the NgRx Data system. +You should rarely (if ever) set it yourself. + +The primary use case for `error` is to catch reducer exceptions. +_NgRx_ stops subscribing to reducers if one of them throws an exception. +Catching reducer exceptions allows the application to continue operating. + +NgRx Data traps an error thrown by an `EntityCollectionReducer` and sets the `EntityAction.error` property to the caught error object. + +The `error` property is important when the errant action is a _persistence action_ (such as `SAVE_ADD_ONE`). +The `EntityEffects` will see that such an action has an error and will return the corresponding failure action (`SAVE_ADD_ONE_ERROR`) immediately, without attempting an HTTP request. + + + +This is the only way we've found to prevent a bad action from getting through the effect and triggering an HTTP request. + + + + + +## _EntityAction.skip_ + +The `skip` property tells downstream action receivers that they should skip the usual action processing. +This flag is usually missing and is implicitly false. + +The NgRx Data sets `skip=true` when you try to delete a new entity that has not been saved. +When the `EntityEffects.persist$` method sees this flag set true on the `EntityAction` envelope, +it skips the HTTP request and dispatches an appropriate `_SUCCESS` action with the +original request payload. + +This feature allows NgRx Data to avoid making a DELETE request when you try to delete an entity +that has been added to the collection but not saved. +Such a request would have failed on the server because there is no such entity to delete. + +See the [`EntityChangeTracker`](guide/data/entity-change-tracker) page for more about change tracking. + + + +## EntityCache-level actions + +A few actions target the entity cache as a whole. + +`SET_ENTITY_CACHE` replaces the entire cache with the object in the action payload, +effectively re-initializing the entity cache to a known state. + +`MERGE_ENTITY_CACHE` replaces specific entity collections in the current entity cache +with those collections present in the action payload. +It leaves the other current collections alone. + +Learn about them in the "[EntityReducer](guide/data/entity-reducer#entity-cache-actions)" document. + + +# EntityChangeTracker + +NgRx Data tracks entity changes that haven't yet been saved on the server. +It also preserves "original values" for these changes and you can revert them with _undo actions_. + +Change-tracking and _undo_ are important for applications that make _optimistic saves_. + +## Optimistic versus Pessimistic save + +An _optimistic save_ stores a new or changed entity in the cache _before making a save request to the server_. +It also removes an entity from the store _before making a delete request to the server_. + + + +The `EntityAction.isOptimistic` flag is one of the `EntityActionOptions`. Set it to override the action's default optimistic or pessimistic behavior. + + + +Many apps are easier to build when saves are "optimistic" because +the changes are immediately available to application code that is watching collection selectors. +The app doesn't have to wait for confirmation that the entity operation succeeded on the server. + +A _pessimistic save_ doesn't update the store until the server confirms that the save succeeded, +which NgRx Data then turns into a "SUCCESS" action that updates the collection. +With a _pessimistic_ save, the changes won't be available in the store + +This confirmation cycle can (and usually will) take significant time and the app has to account for that gap somehow. +The app could "freeze" the UX (perhaps with a modal spinner and navigation guards) until the confirmation cycle completes. +That's tricky code to write and race conditions are inevitable. +And it's difficult to hide this gap from the user and keep the user experience responsive. + +This isn't a problem with optimistic saves because the changed data are immediately available in the store. + + + +The developer always has the option to wait for confirmation of an optimistic save. +But the changed entity data will be in the store during that wait. + + + +### Save errors + +The downside of optimistic save is that _the save could fail_ for many reasons including lost connection, +timeout, or rejection by the server. + +When the client or server rejects the save request, +the _nrgx_ `EntityEffect.persist$` dispatches an error action ending in `_ERROR`. + +**The default entity reducer methods do nothing with save errors.** + +There is no issue if the operation was _pessimistic_. +The collection had not been updated so there is no obvious inconsistency between the state +of the entity in the collection and on the server. + +If the operation was _optimistic_, the entity in the cached collection has been added, removed, or updated. +The entity and the collection are no longer consistent with the state on the server. + +That may be a problem for your application. +If the save fails, the entity in cache no longer accurately reflects the state of the entity on the server. +While that can happen for other reasons (e.g., a different user changed the same data), +when you get a save error, you're almost certainly out-of-sync and should be able to do something about it. + +Change tracking gives the developer the option to respond to a server error +by dispatching an _undo action_ for the entity (or entities) and +thereby reverting the entity (or entities) to the last known server state. + +_Undo_ is NOT automatic. +You may have other save error recovery strategies that preserve the user's +unsaved changes. +It is up to you if and when to dispatch one of the `UNDO_...` actions. + +## Change Tracking + +The NgRx Data tracks an entity's change-state in the collection's `changeState` property. + +When change tracking is enabled (the default), the `changeState` is a _primary key to_ `changeState` _map_. + + + +You can disable change tracking for an individual action or the collection as a whole as +described [below](#disable-change-tracking). + + + +### _ChangeState_ + +A `changeState` map adheres to the following interface + + + +```ts +export interface ChangeState { + changeType: ChangeType; + originalValue: T | undefined; +} + +export enum ChangeType { + Unchanged, // the entity has not been changed. + Added, // the entity was added to the collection + Updated, // the entity in the collection was updated + Deleted, // the entity is scheduled for delete and was removed from collection. +} +``` + + + +A _ChangeState_ describes an entity that changed since its last known server value. +The `changeType` property tells you how it changed. + + + +`Unchanged` is an _implied_ state. +Only changed entities are recorded in the collection's `changeState` property. +If an entity's key is not present, assume it is `Unchanged` and has not changed since it was last +retrieved from or successfully saved to the server. + + + +The _original value_ is the last known value from the server. +The `changeState` object holds an entity's _original value_ for _two_ of these states: _Updated_ and _Deleted_. +For an _Unchanged_ entity, the current value is the original value so there is no need to duplicate it. +There could be no original value for an entity this is added to the collection but no yet saved. + +## EntityActions and change tracking. + +The collection is created with an empty `changeState` map. + +### Recording a change state + +Many _EntityOp_ reducer methods will record an entity's change state. +Once an entity is recorded in the `changeState`, its `changeType` and `originalValue` generally do not change. +Once "added", "deleted" or "updated", an entity stays +that way until committed or undone. + +Delete (remove) is a special case with special rules. +[See below](#delete). + +Here are the most important `EntityOps` that record an entity in the `changeState` map: + + + +```ts +// Save operations when isOptimistic flag is true +SAVE_ADD_ONE; +SAVE_ADD_MANY; +SAVE_DELETE_ONE; +SAVE_DELETE_MANY; +SAVE_UPDATE_ONE; +SAVE_UPDATE_MANY; +SAVE_UPSERT_ONE; +SAVE_UPSERT_MANY; + +// Cache operations +ADD_ONE; +ADD_MANY; +REMOVE_ONE; +REMOVE_MANY; +UPDATE_ONE; +UPDATE_MANY; +UPSERT_ONE; +UPSERT_MANY; +``` + + + +### Removing an entity from the _changeState_ map. + +An entity which has no entry in the `ChangeState` map is presumed to be unchanged. + +The _commit_ and _undo_ operations remove entries from the `ChangeState` which means, in effect, that they are "unchanged." + +The **commit** operations simply remove entities from the `changeState`. +They have no other effect on the collection. + +The [**undo** operations](#undo) replace entities in the collection based on +information in the `changeState` map, reverting them their last known server-side state, and removing them from the `changeState` map. +These entities become "unchanged." + +An entity ceases to be in a changed state when the server returns a new version of the entity. +Operations that put that entity in the store also remove it from the `changeState` map. + +Here are the operations that remove one or more specified entities from the `changeState` map. + + + +```ts +QUERY_ALL_SUCCESS; +QUERY_BY_KEY_SUCCESS; +QUERY_LOAD_SUCCESS; +QUERY_MANY_SUCCESS; +SAVE_ADD_ONE_SUCCESS; +SAVE_ADD_MANY_SUCCESS; +SAVE_DELETE_ONE_SUCCESS; +SAVE_DELETE_MANY_SUCCESS; +SAVE_UPDATE_ONE_SUCCESS; +SAVE_UPDATE_MANY_SUCCESS; +SAVE_UPSERT_ONE_SUCCESS; +SAVE_UPSERT_MANY_SUCCESS; +COMMIT_ONE; +COMMIT_MANY; +UNDO_ONE; +UNDO_MANY; +``` + + + +### Operations that clear the _changeState_ map. + +The `EntityOps` that replace or remove every entity in the collection also reset the `changeState` to an empty object. +All entities in the collection (if any) become "unchanged". + + + +```ts +ADD_ALL; +QUERY_LOAD_SUCCESS; +REMOVE_ALL; +COMMIT_ALL; +UNDO_ALL; +``` + + + +Two of these may surprise you. + +1. `ADD_ALL` is interpreted as a cache load from a known state. + These entities are presumed _unchanged_. + If you have a different intent, use `ADD_MANY`. + +2. `REMOVE_ALL` is interpreted as a cache clear with nothing to save. If you have a different intent, use _removeMany_. + +You can (re)set the `changeState` to anything with `EntityOp.SET_CHANGE_STATE`. + +This is a super-powerful operation that you should rarely perform. +It's most useful if you've created your own entity action and are +modifying the collection in some unique way. + + + +## _Undo_ (revert) an unsaved change + +You have many options for handling an optimistic save error. +One of them is to revert the change to the entity's last known state on the server by dispatching an _undo_ action. + +There are three _undo_ `EntityOps` that revert entities: +`UNDO_ONE`, `UNDO_MANY` and `UNDO_ALL`. + +For `UNDO_ONE` and `UNDO_MANY`, the id(s) of the entities to revert are in the action payload. + +`UNDO_ALL` reverts every entity in the `changeState` map. + +Each entity is reverted as follows: + +- `ADDED` - Remove from the collection and discard + +- `DELETED` - Add the _original value_ of the removed entity to the collection. + If the collection is sorted, it will be moved into place. + If unsorted, it's added to the end of the collection. + +- `UPDATED` - Update the collection with the entity's _original value_. + +If you try to undo/revert an entity whose id is not in the `changeState` map, the action is silently ignored. + + + +### Deleting/removing entities + +There are special change tracking rules for deleting/removing an entity from the collection + +#### Added entities + +When you remove or delete an "added" entity, the change tracker removes the entity from the `changeState` map because there is no server state to which such an entity could be restored. + +The reducer methods that delete and remove entities should immediately remove an _added entity_ from the collection. + + + +The default delete and remove reducer methods remove these entities immediately. + + + +They should not send HTTP DELETE requests to the server because these entities do not exist on the server. + + + +The default `EntityEffects.persist$` effect does not make HTTP DELETE requests for these entities. + + + +#### Updated entities + +An entity registered in the `changeState` map as "updated" +is reclassified as "deleted". +Its `originalValue` stays the same. +Undoing the change will restore the entity to the collection in its pre-update state. + + + +### Merge Strategies + +You can determine how NgRx Data will merge entities after a query, a save, or a cache operation by setting the +entity action's optional `mergeStrategy` property to one of the three strategy enums: + +1. `IgnoreChanges` - Update the collection entities and ignore all change tracking for this operation. Each entity's `changeState` is untouched. + +2. `PreserveChanges` - Updates current values for unchanged entities. + For each changed entity it preserves the current value and overwrites the `originalValue` with the merge entity. + This is the query-success default. + +3. `OverwriteChanges` - Replace the current collection entities. + For each merged entity it discards the `changeState` and sets the `changeType` to "unchanged". + This is the save-success default. + +Disabling change tracking with `IgnoreChanges` is the most frequent choice. + + + +### Disable change tracking + +You can opt-out of change tracking for the entire collection by setting the collection's `noChangeTracking` flag to `true` in its `entityMetadata`. +When `true`, NgRx Data does not track any changes for this collection +and the `EntityCollection.changeState` property remains an empty object. + +You can opt-out of change tracking for a _specific_ entity action by supplying the `mergeStrategy` in the optional `EntityActionOptions` that you can pass in the action payload. + +> If you don't specify a `MergeStrategy`, NgRx Data uses the default for that action. + +If you are dispatching an action with `EntityDispatcher` and you don't want that action to be change-tracked, you might write something like this: + +```typescript +const hero: Hero = { id: 42, name: 'Francis' }; + +dispatcher.addOneToCache(hero, { + mergeStrategy: MergeStrategy.IgnoreChanges, +}); +``` + +You can also pass that option to methods of a helpful `EntityCollectionService` facade such as your custom `HeroService` + +```typescript +const hero: Hero = { id: 42, name: 'Francis' }; + +heroService.addOneToCache(hero, { + mergeStrategy: MergeStrategy.IgnoreChanges, +}); +``` + +If you prepare the `EntityAction` directly with an `EntityActionFactory`, it might look like this: + +```typescript +const hero: Hero = { id: 42, name: 'Francis' }; + +const payload: EntityActionPayload = { + entityName: 'Hero', + entityOp: EntityOp.ADD_ONE, + data: hero, + mergeStrategy: MergeStrategy.IgnoreChanges, + // .. other options .. +}; + +const action = factory.create(payload); +``` + + +# EntityCollectionService + +An **`EntityCollectionService`** is a facade over the NgRx Data **dispatcher** and **selectors$** that manages an entity `T` collection cached in the _NgRx store_. + +The **_Dispatcher_** features **command** methods that dispatch [_entity actions_](guide/data/entity-actions) to the _NgRx store_. +These commands either update the entity collection directly or trigger HTTP requests to a server. When the server responds, the NgRx Data library dispatches new actions with the response data and these actions update the entity collection. + +The `EntityCommands` interface lists all the commands and what they do. + +Your application calls these _command methods_ to update +the _cached entity collection_ in the _NgRx store_. + +**_Selectors$_** are properties returning _selector observables_. +Each _observable_ watches for a specific change in the cached entity collection and emits the changed value. + +The `EntitySelectors$` interface lists all of the pre-defined _selector observable properties_ and +explains which collection properties they observe. + +Your application subscribes to _selector observables_ +in order to process and display entities in the collection. + +## Examples from the demo app + +Here are simplified excerpts from the demo app's `HeroesComponent` showing the component calling _command methods_ and subscribing to _selector observables_. + +```typescript +constructor(EntityCollectionServiceFactory: EntityCollectionServiceFactory) { + this.heroService = EntityCollectionServiceFactory.create('Hero'); + this.filteredHeroes$ = this.heroService.filteredEntities$; + this.loading$ = this.heroService.loading$; +} + +getHeroes() { this.heroService.getAll(); } +add(hero: Hero) { this.heroService.add(hero); } +deleteHero(hero: Hero) { this.heroService.delete(hero.id); } +update(hero: Hero) { this.heroService.update(hero); } +``` + +### Create the _EntityCollectionService_ with a factory + +The component injects the NgRx Data `EntityCollectionServiceFactory` and +creates an `EntityCollectionService` for `Hero` entities. + + + +We'll go inside the factory [later in this guide](#entitycollectionservicefactory). + + + +### Create the _EntityCollectionService_ as a class + +Alternatively, you could have created a single `HeroEntityService` elsewhere, perhaps in the `AppModule`, and injected it into the component's constructor. + +There are two basic ways to create the service class. + +1. Derive from `EntityCollectionServiceBase` +1. Write a `HeroEntityService` with just the API you need. + +When `HeroEntityService` derives from `EntityCollectionServiceBase` it must inject the `EntityCollectionServiceFactory` into its constructor. +There are examples of this approach in the demo app. + +When defining an `HeroEntityService` with a limited API, +you may also inject `EntityCollectionServiceFactory` as a source of the +functionality that you choose to expose. + +Let your preferred style and app needs determine which creation technique you choose. + +### Set component _selector$_ properties + +A `selector$` property is an _observable_ that emits when a _selected_ state property changes. + + + +Some folks refer to such properties as **state streams**. + + + +The example component has two such properties that expose two `EntityCollectionService` _selector observables_: `filteredEntities$` and `loading$`. + +The `filteredEntities$` _observable_ produces an array of the currently cached `Hero` entities that satisfy the user's filter criteria. +This _observable_ produces a new array of heroes if the user +changes the filter or if some action changes the heroes in the cached collection. + +The `loading$` _observable_ produces `true` while the +[data service](guide/data/entity-dataservice) is waiting for heroes from the server. +It produces `false` when the server responds. +The demo app subscribes to `loading$` so that it can turn a visual loading indicator on and off. + + + +These component and `EntityCollectionService` selector property names end in `'$'` which is a common convention for a property that returns an `Observable`. +All _selector observable_ properties of an `EntityCollectionService` follow this convention. + + + +#### The _selector observable_ versus the _selector function_ + +The _`selector$`_ observable (ending with an `'$'`) differs from the similarly named and +closely-related `selector` function (no `'$'` suffix) + +A `selector` is a _function_ that _selects_ a slice of state from the entity collection. +A `selector$` observable emits that slice of state when the state changes. + +NgRx Data creates a `selector$` observable by passing the _selector_ function to the NgRx `select` operator and piping it onto the NgRx store, as seen in the following example: + +```typescript +loading$ = this.store.select(selectLoading); +``` + +#### Using _selectors$_ + +The component _class_ does not subscribe to these `selector$` properties but the component _template_ does. + +The template binds to them and forwards their _observables_ to the Angular `AsyncPipe`, which subscribes to them. +Here's an excerpt of the `filteredHeroes$` binding. + +```html +

    ...
    +``` + +### Call _command methods_ + +Most of the `HeroesComponent` methods delegate to `EntityCollectionService` command methods such as `getAll()` and `add()`. + +There are two kinds of commands: + +1. Commands that trigger requests to the server. +1. Cache-only commands that update the cached entity collection. + +The server commands are simple verbs like "add" and "getAll". +They dispatch actions that trigger asynchronous requests to a remote server. + +The cache-only command methods are longer verbs like "addManyToCache" and "removeOneFromCache" +and their names all contain the word "cache". +They update the cached collection immediately (synchronously). + + + +Most applications call the server commands because they want to query and save entity data. + +Apps rarely call the cache-only commands because direct updates to the entity collection +are lost when the application shuts down. + + + +Many `EntityCollectionService` command methods take a value. +The value is _typed_ (often as `Hero`) so you won't make a mistake by passing in the wrong kind of value. + +Internally, an entity service method creates an +[_entity action_](guide/data/entity-actions) that corresponds to the method's intent. The action's _payload_ is either the value passed to the method or an appropriate derivative of that value. + +_Immutability_ is a core principle of the _redux pattern_. +Several of the command methods take an entity argument such as a `Hero`. +An entity argument **must never be a cached entity object**. +It can be a _copy_ of a cached entity object and it often is. +The demo application always calls these command methods with copies of the entity data. + +All _command methods_ return `void` or an `Observable`. +A core principle of the _redux pattern_ is that _commands_ never return a value. They just _do things_ that have side-effects. +Thus, the action is only dispatched when the command is invoked, and re-subscribing to a command's returned `Observable` will not +dispatch another action. + +Rather than expect a result from the command, +you subscribe to a _selector$_ property that reflects +the effects of the command. If the command did something you care about, a _selector$_ property should be able to tell you about it. + +## _EntityCollectionServiceFactory_ + +The `create()` method of the NgRx Data `EntityCollectionServiceFactory` produces a new instance +of the `EntityCollectionServiceBase` class that implements the `EntityCollectionService` interface for the entity type `T`. + + +# Entity Collection + +The NgRx Data library maintains a _cache_ (`EntityCache`) of +_entity collections_ for each _entity type_ in the _NgRx store_. + +An _entity_collection_ implements the `EntityCollection` interface for an entity type. + +| Property | Meaning | +| ------------- | ----------------------------------------------------------------------------------------------------------- | +| `ids` | Primary key values in default sort order | +| `entities` | Map of primary key to entity data values | +| `filter` | The user's filtering criteria | +| `loaded` | Whether collection was filled by QueryAll; forced false after clear | +| `loading` | Whether currently waiting for query results to arrive from the server | +| `changeState` | When [change-tracking](guide/data/entity-change-tracker) is enabled, the `ChangeStates` of unsaved entities | + +You can extend an entity types with _additional properties_ via +[entity metadata](guide/data/entity-metadata#additionalcollectionstate). + + +# Entity DataService + +The NgRx Data library expects to persist entity data with calls to a REST-like web api with endpoints for each entity type. + +The `EntityDataService` maintains a registry of service classes dedicated to persisting data for a specific entity type. + +When the NgRx Data library sees an action for an entity _persistence operation_, it asks the `EntityDataService` for the registered data service that makes HTTP calls for that entity type, and calls the appropriate service method. + +A data service is an instance of a class that implements the `EntityCollectionDataService`. +This interface supports a basic set of CRUD operations for an entity. +Each that return `Observables`: + +| Method | Meaning | HTTP Method with endpoint | +| ---------------------------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------- | +| `add(entity: T): Observable, httpOptions?: HttpOptions` | Add a new entity | `POST` /api/hero/ | +| `delete(id: number` | `string, httpOptions?: HttpOptions): Observable` | Delete an entity by primary key value | `DELETE` /api/hero/5 | +| `getAll(httpOptions?: HttpOptions): Observable` | Get all instances of this entity type | `GET` /api/heroes/ | +| `getById(id: number` | `string, httpOptions?: HttpOptions): Observable` | Get an entity by its primary key | `GET` /api/hero/5 | +| `getWithQuery(queryParams: QueryParams` | `string, httpOptions?: HttpOptions): Observable` | Get entities that satisfy the query | `GET` /api/heroes/?name=bombasto | +| `update(update: Update, httpOptions?: HttpOptions): Observable` | Update an existing entity | `PUT` /api/hero/5 | +| `upsert(entity: T, httpOptions?: HttpOptions): Observable` | Upsert an entity (if api supports upsert) | `POST` /api/hero/5 | + + + +`QueryParams` is a _parameter-name/value_ map +You can also supply the query string itself. +`HttpClient` safely encodes both into an encoded query string. + +`Update` is an object with a strict subset of the entity properties. +It _must_ include the properties that participate in the primary key (e.g., `id`). +The update property values are the _properties-to-update_; +unmentioned properties should retain their current values. + +`HttpOptions` is an object containing properties that will be forwarded on +to the Data Service's http requests. The `DefaultDataService` uses this data to create `HttpHeaders` and `HttpParams` +to pass to `HttpClient` requests. This allows the configuration of Http Query Parameters and/or +Http Headers from the `EntityCollectionDataService` api. + + + +The default data service methods return the `Observables` returned by the corresponding Angular `HttpClient` methods. + +Your API should return an object in the shape of the return type for each data service method. For example: when calling `.add(entity)` your API +should create the entity and then return the full entity matching `T` as that is the value that will be set as the record in the store for that entities primary +key. The one method that differs from the others is `delete`. `delete` requires a response type of the entities primary key, `string | number`, instead of the full object, `T`, that was deleted. + + + +If you create your own data service alternatives, they should return similar `Observables`. + + + +## Register data services + +The `EntityDataService` registry is empty by default. + +You can add custom data services to it by creating instances of those classes and registering them with `EntityDataService` in one of two ways. + +1. register a single data service by entity name with the `registerService()` method. + +2. register several data services at the same time with by calling `registerServices` with an _entity-name/service_ map. + + + +You can create and import a module that registers your custom data services as shown in the _EntityDataService_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/dataservices/entity-data.service.spec.ts) + + + +If you decide to register an entity data service, be sure to do so _before_ you ask NgRx Data to perform a persistence operation for that entity. + +Otherwise, the NgRx Data library will create and register an instance of the default data service `DefaultDataService` for that entity type. + +## The _DefaultDataService_ + +The demo app doesn't register any entity data services. +It relies entirely on a `DefaultDataService`, created for each entity type, by the injected `DefaultDataServiceFactory`. + +A `DefaultDataService` makes REST-like calls to the server's web api with Angular's `HttpClient`. + +It composes HTTP URLs from a _root_ path (see ["Configuration"](#configuration) below) and the entity name. + +For example, + +- if the persistence action is to delete a hero with id=42 _and_ +- the root path is `'api'` _and_ +- the entity name is `'Hero'`, _then_ +- the DELETE request URL will be `'api/hero/42'`. + +When the persistence operation concerns multiple entities, the `DefaultDataService` substitutes the plural of the entity type name for the resource name. + +The `QUERY_ALL` action to get all heroes would result in an HTTP GET request to the URL `'api/heroes'`. + +The `DefaultDataService` doesn't know how to pluralize the entity type name. +It doesn't even know how to create the base resource names. + +It relies on an injected `HttpUrlGenerator` service to produce the appropriate endpoints. +And the default implementation of the `HttpUrlGenerator` relies on the +`Pluralizer` service to produce the plural collection resource names. + +The [_Entity Metadata_](guide/data/entity-metadata#plurals) guide +explains how to configure the default `Pluralizer` . + + + +### Configure the _DefaultDataService_ + +The collection-level data services construct their own URLs for HTTP calls. They typically rely on shared configuration information such as the root of every resource URL. + +The shared configuration values are almost always specific to the application and may vary according the runtime environment. + +The NgRx Data library defines a `DefaultDataServiceConfig` for +conveying shared configuration to an entity collection data service. + +The most important configuration property, `root`, returns the _root_ of every web api URL, the parts that come before the entity resource name. If you are using a remote API, this value can include the protocol, domain, port, and root path, such as `https://my-api-domain.com:8000/api/v1`. + +For a `DefaultDataService`, the default value is `'api'`, which results in URLs such as `api/heroes`. + +The `timeout` property sets the maximum time (in ms) before the _ng-lib_ persistence operation abandons hope of receiving a server reply and cancels the operation. The default value is `0`, which means that requests do not timeout. + +The `delete404OK` flag tells the data service what to do if the server responds to a DELETE request with a `404 - Not Found`. + +In general, not finding the resource to delete is harmless and +you can save yourself the headache of ignoring a DELETE 404 error +by setting this flag to `true`, which is the default for the `DefaultDataService`. + +When running a demo app locally, the server may respond more quickly than it will in production. You can simulate real-world by setting the `getDelay` and `saveDelay` properties. + +#### Provide a custom configuration + +First, create a custom configuration object of type `DefaultDataServiceConfig` : + +```typescript +const defaultDataServiceConfig: DefaultDataServiceConfig = { + root: 'https://my-api-domain.com:8000/api/v1', + timeout: 3000, // request timeout +}; +``` + +Provide it in an eagerly-loaded `NgModule` such as the `EntityStoreModule` in the sample application: + +```typescript +@NgModule({ + providers: [{ provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig }] +}) +``` + +## Custom _EntityDataService_ + +While the NgRx Data library provides a configuration object to modify certain aspects of the _DefaultDataService_, +you may want to further customize what happens when you save or retrieve data for a particular collection. + +For example, you may need to modify fetched entities to convert strings to dates, or to add additional properties to an entity. + +You could do this by creating a custom data service and registering that service with the `EntityDataService`. + +To illustrate this, the sample app adds a `dateLoaded` property to the `Hero` entity to record when a hero is loaded from the server into the _NgRx-store_ entity cache. + +```typescript +export class Hero { + readonly id: number; + readonly name: string; + readonly saying: string; + readonly dateLoaded: Date; +} +``` + +To support this feature, we 'll create a `HeroDataService` class that implements the `EntityCollectionDataService` interface. + +In the sample app the `HeroDataService` derives from the NgRx Data `DefaultDataService` in order to leverage its base functionality. +It only overrides what it really needs. + + + +```ts +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + EntityCollectionDataService, + DefaultDataService, + HttpUrlGenerator, + Logger, + QueryParams, +} from '@ngrx/data'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Hero } from '../../core'; + +@Injectable() +export class HeroDataService extends DefaultDataService { + constructor( + http: HttpClient, + httpUrlGenerator: HttpUrlGenerator, + logger: Logger + ) { + super('Hero', http, httpUrlGenerator); + logger.log('Created custom Hero EntityDataService'); + } + + getAll(): Observable { + return super + .getAll() + .pipe( + map((heroes) => heroes.map((hero) => this.mapHero(hero))) + ); + } + + getById(id: string | number): Observable { + return super.getById(id).pipe(map((hero) => this.mapHero(hero))); + } + + getWithQuery(params: string | QueryParams): Observable { + return super + .getWithQuery(params) + .pipe( + map((heroes) => heroes.map((hero) => this.mapHero(hero))) + ); + } + + private mapHero(hero: Hero): Hero { + return { ...hero, dateLoaded: new Date() }; + } +} +``` + + + +This `HeroDataService` hooks into the _get_ operations to set the `Hero.dateLoaded` on fetched hero entities. +It also tells the logger when it is created (see the console output of the running sample) . + +Finally, we must tell NgRx Data about this new data service. + +The sample app provides `HeroDataService` and registers it by calling the `registerService()` method on the `EntityDataService` in the app's _entity store module_: + + + +```ts +import { EntityDataService } from '@ngrx/data'; // <-- import the NgRx Data data service registry + +import { HeroDataService } from './hero-data-service'; + +@NgModule({ + imports: [ ... ], + providers: [ HeroDataService ] // <-- provide the data service +}) +export class EntityStoreModule { + constructor( + entityDataService: EntityDataService, + heroDataService: HeroDataService, + ) { + entityDataService.registerService('Hero', heroDataService); // <-- register it + } +} +``` + + + +### A custom _DataService_ + +You don't have to override members of the `DefaultDataService`. +You could write a completely custom alternative that queries and saves +entities by any mechanism you choose. + +You can register it the same way as long as it adheres to the interface. + +```typescript +// Register custom data service +entityDataService.registerService('Hero', peculiarHeroDataService); +``` + + +# Entity Effects + +**Work in Progress** + +**_Effects_** are a way to trigger _side effects_ with _actions_. + +A one common, desirable _side effect_ is an asynchronous HTTP call to the remote server to fetch or save entity data. + +You implement one or more _effects_ with the help of the NgRx [Effects](guide/effects) library. + +_Actions_ dispatched to the _NgRx store_ can be detected and processed by your _effect_ method. +After processing, whether synchronously or asynchronously, your method can dispatch new action(s) to the _store_ + +The NgRx Data library implements an effect named `persist$` in its `EntityEffects` class. + +The `persist$` method filters for certain `EntityAction.op` values. +These values turn into HTTP GET, PUT, POST, and DELETE requests with entity data. +When the server responds (whether favorably or with an error), the `persist$` method dispatches new `EntityAction`s to the _store_ with the appropriate response data. + +#### Cancellation + +You can try to cancel a save by dispatching a `CANCEL_PERSIST` EntityAction with the +**correlation id** of the _persistence action_ that you want to cancel. + +The `EntityCache.cancel$` watches for this action and is piped into +the `EntityCache.persist$`, where it can try to cancel an entity collection query or save operation +or at least prevent the server response from updating the cache. + + + +It's not obvious that this is ever a great idea for a save operation. +You cannot tell the server to cancel this way and cannot know if the server did or did not save. +Nor can you count on processing the cancel request before the client receives the server response +and applies the changes on the server or to the cache. + +If you cancel before the server results arrive, the `EntityEffect` will not try to update +the cached collection with late arriving server results. +The effect will issue a `CANCELED_PERSIST` action instead. +The `EntityCollection` reducer ignores this action but you can listen for it among the store actions +and thus know that the cancellation took effect on the client. + + + +**_More to come on the subject of effects_** + + +# Entity Metadata + +The NgRx Data library maintains a **_cache_** of entity collection data in the _NgRx store_. + +You tell the NgRx Data library about those collections and the entities they contain with **_entity metadata_**. + +The entities within a collection belong to the same **_entity type_**. +Each _entity type_ appears as named instance of the NgRx Data [**`EntityMetadata`**](#metadata-properties) interface. + +You can specify metadata for several entities at the same time in an **`EntityMetadataMap`**. + +Here is an example `EntityMetadataMap` similar to the one in the demo app +that defines metadata for two entities, `Hero` and `Villain`. + + + +```ts +export const appEntityMetadata: EntityMetadataMap = { + Hero: { + /* optional settings */ + filterFn: nameFilter, + sortComparer: sortByName, + }, + Villain: { + villainSelectId, // necessary if key is not `id` + + /* optional settings */ + entityName: 'Villain', // optional because same as map key + filterFn: nameAndSayingFilter, + entityDispatcherOptions: { + optimisticAdd: true, + optimisticUpdate: true, + }, + }, +}; +``` + + + +## Register metadata + +You must register the metadata with the NgRx Data `EntityDefinitionService`. + +The easiest way to register metadata is to define a single `EntityMetadataMap` for the entire application and specify it in the one place where you initialize the NgRx Data library: + +```typescript + EntityDataModule.forRoot({ + ... + entityMetadata: appEntityMetadata, + ... + }) +``` + +If you define entities in several, different _eagerly-loaded_ Angular modules, you can add the metadata for each module with the multi-provider. + +```typescript +{ provide: ENTITY_METADATA_TOKEN, multi: true, useValue: someEntityMetadata } +``` + +This technique won't work for a _lazy-loaded_ module. +The `ENTITY_METADATA_TOKEN` provider was already set and consumed by the time the _lazy-loaded_ module arrives. + +The module should inject the `EntityDefinitionService` +instead and register metadata directly with one of the registration methods. + +```typescript +@NgModule({...}) +class LazyModule { + constructor(eds: EntityDefinitionService) { + eds.registerMetadataMap(this.lazyMetadataMap); + } + ... +} +``` + +## Metadata Properties + +The `EntityMetadata` interface describes aspects of an entity type that tell the NgRx Data library how to manage collections of entity data of type `T`. + +Type `T` is your application's TypeScript representation of that entity; it can be an interface or a class. + +### _entityName_ + +The `entityName` of the type is the only **required metadata property**. +It's the unique _key_ of the entity type's metadata in cache. + +It _must_ be specified for individual `EntityMetadata` instances. +If you omit it in an `EntityMetadataMap`, the map _key_ becomes the `entityName` as in this example. + +```typescript +const map = { + Hero: {}, // "Hero" becomes the entityName +}; +``` + +The spelling and case (typically PascalCase) of the `entityName` is important for NgRx Data conventions. It appears in the generated [_entity actions_](guide/data/entity-actions), in error messages, and in the persistence operations. + +Importantly, the default [_entity dataservice_](guide/data/entity-dataservice) creates HTTP resource URLs from the lowercase version of this name. For example, if the `entityName` is "Hero", the default data service will POST to a URL such as `'api/hero'`. + + + +By default it generates the _plural_ of the entity name when preparing a _collection_ resource URL. + +It isn't good at pluralization. +It would produce `'api/heros'` for the URL to fetch _all heroes_ because it blindly adds an `'s'` to the end of the lowercase entity name. + +Of course the proper plural of "hero" is "hero**es**", not "hero**s**". +You'll see how to correct this problem [below](#plurals). + + + +### _filterFn_ + +Many applications allow the user to filter a cached entity collection. + +In the accompanying demonstration app, the user can filter _heroes_ by name and can filter _villains_ by name or the villain's _saying_. + +We felt this common scenario is worth building into the NgRx Data library. So every entity can have an _optional_ filter function. + +Each collection's `filteredEntities` selector applies the filter function to the collection, based on the user's filtering criteria, which are held in the stored entity collection's `filter` property. + +If there is no filter function, the `filteredEntities` selector is the same as the `selectAll` selector, which returns all entities in the collection. + +A filter function (see `EntityFilterFn`) takes an entity collection and the user's filtering criteria (the filter _pattern_) and returns an array of the selected entities. + +Here's an example that filters for entities with a `name` property whose value contains the search string. + +```typescript +export function nameFilter( + entities: { name: string }[], + search: string +) { + return entities.filter((e) => -1 < e.name.indexOf(search)); +} +``` + +The NgRx Data library includes a helper function, `PropsFilterFnFactory`, that creates an entity filter function which will treat the user's input +as a case-insensitive, regular expression and apply it to one or more properties of the entity. + +The demo uses this helper to create hero and villain filters. Here's how the app creates the `nameAndSayingFilter` function for villains. + +```typescript +/** + * Filter for entities whose name or saying + * matches the case-insensitive pattern. + */ +export function nameAndSayingFilter( + entities: Villain[], + pattern: string +) { + return ( + PropsFilterFnFactory < + Villain > + ['name', 'saying'](entities, pattern) + ); +} +``` + +### _selectId_ + +Every _entity type_ must have a _primary key_ whose value is an integer or a string. + +The NgRx Data library assumes that the entity has an `id` property whose value is the primary key. + +Not every entity will have a primary key property named `id`. For some entities, the primary key could be the combined value of two or more properties. + +In these cases, you specify a `selectId` function that, given an entity instance, returns an integer or string primary key value. + +In the _EntityCollectionReducer_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/reducers/entity-collection-reducer.spec.ts), +the `Villain` type has a string primary key property named `key`. +The `selectorId` function is this: + +```typescript +selectId: (villain: Villain) => villain.key; +``` + +### _sortComparer_ + +The NgRx Data library keeps the collection entities in a specific order. + + + +This is actually a feature of the underlying NgRx Entity library. + + + +The default order is the order in which the entities arrive from the server. +The entities you add are pushed to the end of the collection. + +You may prefer to maintain the collection in some other order. +When you provide a `sortComparer` function, the _NgRx-lib_ keeps the collection in the order prescribed by your comparer. + +In the demo app, the villains metadata has no comparer so its entities are in default order. + +The hero metadata have a `sortByName` comparer that keeps the collection in alphabetical order by `name`. + +```typescript +export function sortByName( + a: { name: string }, + b: { name: string } +): number { + return a.name.localeCompare(b.name); +} +``` + +Run the demo app and try changing existing hero names or adding new heroes. + +Your app can call the `selectKey` selector to see the collection's `ids` property, which returns an array of the collection's primary key values in sorted order. + +### _entityDispatcherOptions_ + +These options determine the default behavior of the collection's _dispatcher_ which sends actions to the reducers and effects. + +A dispatcher save command will add, delete, or update +the collection _before_ sending a corresponding HTTP request (_optimistic_) or _after_ (_pessimistic_). +The caller can specify in the optional `isOptimistic` parameter. +If the caller doesn't specify, the dispatcher chooses based on default options. + +The _defaults_ are the safe ones: _optimistic_ for delete and _pessimistic_ for add and update. +You can override those choices here. + +### _additionalCollectionState_ + +Each NgRx Data entity collection in the store has +[predefined properties](guide/data/entity-collection). + +You can add your own collection properties by setting the `additionalCollectionState` property to an object with those custom collection properties. + +The _EntitySelectors_ [tests](https://github.com/ngrx/platform/blob/main/modules/data/spec/selectors/entity-selectors.spec.ts) +illustrate by adding `foo` and `bar` collection properties to test hero metadata. + +```typescript + additionalCollectionState: { + foo: 'Foo', + bar: 3.14 + } +``` + +The property values become the initial collection values for those properties when NgRx Data first creates the collection in the store. + +The NgRx Data library generates selectors for these properties, but has no way to update them. You'll have to create or extend the existing reducers to do that yourself. + +If the property you want to add comes from `backend`, you will need some additional work to make sure the property can be saved into the store from `Effects` correctly. + +#### Step 1: Implement `PersistenceResultHandler` to save data from backend to action.payload + +Create a new class `AdditionalPersistenceResultHandler` that `extends DefaultPersistenceResultHandler` and overwrite the [handleSuccess](https://github.com/ngrx/platform/blob/main/modules/data/src/dataservices/persistence-result-handler.service.ts) method, the purpose is to parse the data received from `DataService`, retrieve the additional property, and then save this to the `action.payload`. Note that the default reducer for success actions requires that `action.payload.data` is an array of entities or an entity. This would need to be set after retrieving the additional property, not shown in the example below. + +```typescript +export class AdditionalPersistenceResultHandler extends DefaultPersistenceResultHandler { + handleSuccess(originalAction: EntityAction): (data: any) => Action { + const actionHandler = super.handleSuccess(originalAction); + // return a factory to get a data handler to + // parse data from DataService and save to action.payload + return function (data: any) { + const action = actionHandler.call(this, data); + if (action && data && data.foo) { + // save the data.foo to action.payload.foo + (action as any).payload.foo = data.foo; + } + return action; + }; + } +} +``` + +#### Step 2: Overwrite `EntityCollectionReducerMethods` to save the additional property from action.payload to the EntityCollection instance + +Following the prior step, we have added the additional property to the `action.payload`. Up next we need to set it to the instance of EntityCollection in the `reducer`. In order to accomplish that, we need to create an `AdditionalEntityCollectionReducerMethods` that `extends EntityCollectionReducerMethods`. In addition, we will need to overwrite the method to match your `action`. For example, if the additional property `foo` is only available in `queryMany action(triggered by EntityCollectionService.getWithQuery)`, we can follow this approach. + +```typescript +export class AdditionalEntityCollectionReducerMethods< + T +> extends EntityCollectionReducerMethods { + constructor( + public entityName: string, + public definition: EntityDefinition + ) { + super(entityName, definition); + } + protected queryManySuccess( + collection: EntityCollection, + action: EntityAction + ): EntityCollection { + const ec = super.queryManySuccess(collection, action); + if ((action.payload as any).foo) { + // save the foo property from action.payload to entityCollection instance + (ec as any).foo = (action.payload as any).foo; + } + return ec; + } +} +``` + +#### Step 3: Register customized `EntityCollectionReducerMethods` and `AdditionalPersistenceResultHandler`. + +Finally we need to register the `AdditionalPersistenceResultHandler` and `AdditionalEntityCollectionReducerMethods` to replace the default implementation. + +Register `AdditionalPersistenceResultHandler` in `NgModule`, + +```typescript +@NgModule({ + { provide: PersistenceResultHandler, useClass: AdditionalPersistenceResultHandler }, +}) +``` + +Register `AdditionalEntityCollectionReducerMethods`, to do that, we need to create an `AdditionalEntityCollectionReducerMethodFactory`, for details, see [Entity Reducer](guide/data/entity-reducer) + +```typescript +@Injectable() +export class AdditionalEntityCollectionReducerMethodsFactory { + constructor( + private entityDefinitionService: EntityDefinitionService + ) {} + /** Create the {EntityCollectionReducerMethods} for the named entity type */ + create(entityName: string): EntityCollectionReducerMethodMap { + const definition = + this.entityDefinitionService.getDefinition(entityName); + const methodsClass = new AdditionalEntityCollectionReducerMethods( + entityName, + definition + ); + return methodsClass.methods; + } +} +``` + +Register `AdditionalEntityCollectionReducerMethodsFactory` to `NgModule`, + +```typescript +@NgModule({ + { + provide: EntityCollectionReducerMethodsFactory, + useClass: AdditionalEntityCollectionReducerMethodsFactory + }, +}) +``` + +Now you can get `foo` from `backend` just like another `EntityCollection` level property. + + + +## Pluralizing the entity name + +The NgRx Data [`DefaultDataService`](guide/data/entity-dataservice) relies on the `HttpUrlGenerator` to create conventional HTTP resource names (URLs) for each entity type. + +By convention, an HTTP request targeting a single entity item contains the lowercase, singular version of the entity type name. For example, if the entity type `entityName` is "Hero", the default data service will POST to a URL such as `'api/hero'`. + +By convention, an HTTP request targeting multiple entities contains the lowercase, _plural_ version of the entity type name. The URL of a GET request that retrieved all heroes should be something like `'api/heroes'`. + +The `HttpUrlGenerator` can't pluralize the entity type name on its own. It delegates to an injected _pluralizing class_, called `Pluralizer`. + +The `Pluralizer` class has a _pluralize()_ method that takes the singular string and returns the plural string. + +The default `Pluralizer` handles many of the common English pluralization rules such as appending an `'s'`. +That's fine for the `Villain` type (which becomes "Villains") and even for `Company` (which becomes "Companies"). + +It's far from perfect. For example, it incorrectly turns `Hero` into "Heros" instead of "Heroes". + +Fortunately, the default `Pluralizer` also injects a map of singular to plural strings (with the `PLURAL_NAMES_TOKEN`). + +Its `pluralize()` method looks for the singular entity name in that map and uses the corresponding plural value if found. +Otherwise, it returns the default pluralization of the entity name. + +If this scheme works for you, create a map of _singular-to-plural_ entity names for the exceptional cases: + +```typescript +export const pluralNames = { + // Case matters. Match the case of the entity name. + Hero: 'Heroes', +}; +``` + +Then specify this map while configuring the NgRx Data library. + +```typescript + EntityDataModule.forRoot({ + ... + pluralNames: pluralNames + }) +``` + +If you define your _entity model_ in separate Angular modules, you can incrementally add a plural names map with the multi-provider. + +```typescript +{ provide: PLURAL_NAMES_TOKEN, multi: true, useValue: morePluralNames } +``` + +If this scheme isn't working for you, replace the `Pluralizer` class with your own invention. + +```typescript +{ provide: Pluralizer, useClass: MyPluralizer } +``` + + +# Entity Reducer + +The _Entity Reducer_ is the _master reducer_ for all entity collections in the stored entity cache. + + + +The library doesn't have a named _entity reducer_ type. +Rather it relies on the **`EntityCacheReducerFactory.create()`** method to produce that reducer, +which is an _NgRx_ `ActionReducer`. + +Such a reducer function takes an `EntityCache` state and an `EntityAction` action +and returns an `EntityCache` state. + +The reducer responds either to an [EntityCache-level action](#entity-cache-actions) (rare) +or to an `EntityAction` targeting an entity collection (the usual case). +All other kinds of `Action` are ignored and the reducer simply returns the given `state`. + + + +The reducer filters specifically for the action's `entityType` property. +It treats any action with an `entityType` property as an `EntityAction`. + + + +The _entity reducer's_ primary job is to + +- extract the `EntityCollection` for the action's entity type from the `state`. +- create a new, [initialized entity collection](#initialize) if necessary. +- get or create the `EntityCollectionReducer` for that entity type. +- call the _entity collection reducer_ with the collection and the action. +- replace the _entity collection_ in the `EntityCache` with the new collection returned by the _entity collection reducer_. + +## _EntityCollectionReducers_ + +An `EntityCollectionReducer` applies _actions_ to an `EntityCollection` in the `EntityCache` held in the _NgRx store_. + +There is always a reducer for a given entity type. +The `EntityCollectionReducerFactory` maintains a registry of them. +If it can't find a reducer for the entity type, it [creates one](#collection-reducer-factory), with the help +of the injected `EntityCollectionReducerFactory`, and registers that reducer +so it can use it again next time. + + + +### Register custom reducers + +You can create a custom reducer for an entity type and +register it directly with `EntityCollectionReducerRegistry.registerReducer()`. + +You can register several custom reducers at the same time +by calling `EntityCollectionReducerRegistry.registerReducers(reducerMap)` where +the `reducerMap` is a hash of reducers, keyed by _entity-type-name_. + + + +## Default _EntityCollectionReducer_ + +The `EntityCollectionReducerFactory` creates a default reducer that leverages +the capabilities of the NgRx `EntityAdapter`, +guided by the app's [_entity metadata_](guide/data/entity-metadata). + +The default reducer decides what to do based on the `EntityAction.op` property,whose string value it expects will be a member of the `EntityOp` enum. + +Many of the `EntityOp` values are ignored; the reducer simply returns the +_entity collection_ as given. + +Certain persistence-oriented ops, for example, +are meant to be handled by the NgRx Data [`persist$` effect](guide/data/entity-effects). +They don't update the collection data (other than, perhaps, to flip the `loading` flag). + +Others add, update, and remove entities from the collection. + + + +Remember that _immutable objects_ are a core principle of the _redux/NgRx_ pattern. +These reducers don't actually change the original collection or any of the objects in it. +They make a copy of the collection and only update copies of the objects within the collection. + + + +See the NgRx Entity [`EntityAdapter` collection methods](guide/entity/adapter#adapter-collection-methods) for a basic guide to the +cache altering operations performed by the default _entity collection reducer_. + +The `EntityCollectionReducerFactory` class and its tests are the authority on how the default reducer actually works. + + + +## Initializing collection state + +The `NgRxDataModule` adds an empty `EntityCache` to the NgRx Data store. +There are no collections in this cache. + +If the master `EntityReducer` can't find a collection for the _action_'s entity type, +it creates a new, initialized collection with the help of the `EntityCollectionCreator`, which was injected into the `EntityCacheReducerFactory`. + +The _creator_ returns an initialized collection from the `initialState` in the entity's `EntityDefinition`. +If the entity type doesn't have a _definition_ or the definition doesn't have an `initialState` property value, +the creator returns an `EntityCollection`. + +The _entity reducer_ then passes the new collection in the `state` argument of the _entity collection reducer_. + + + +## Customizing entity reducer behavior + +You can _replace_ any entity collection reducer by [registering a custom alternative](#register). + +You can _replace_ the default _entity reducer_ by +providing a custom alternative to the [`EntityCollectionReducerFactory`](#collection-reducer-factory). + +You could even _replace_ the master _entity reducer_ by +providing a custom alternative to the [`EntityCacheReducerFactory`](#reducer-factory). + +But quite often you'd like to extend a _collection reducer_ with some additional reducer logic that runs before or after. + + + +## EntityCache-level actions + +A few actions target the entity cache as a whole. + +`SET_ENTITY_CACHE` replaces the entire cache with the object in the action payload, +effectively re-initializing the entity cache to a known state. + +`MERGE_ENTITY_CACHE` replaces specific entity collections in the current entity cache +with those collections present in the action payload. +It leaves the other current collections alone. + + + +See `entity-reducer.spec.ts` for examples of these actions. + + + +These actions might be part of your plan to support offline scenarios or rollback changes to many collections at the same time. + +For example, you could subscribe to the `EntityServices.entityCache$` selector. +When the cache changes, you could +serialize the cache to browser local storage. +You might want to _debounce_ for a few seconds to reduce churn. + +Later, when relaunching the application, you could dispatch the `SET_ENTITY_CACHE` action to initialize the entity-cache even while disconnected. +Or you could dispatch the `MERGE_ENTITY_CACHE` to rollback selected collections to a known state as +in error-recovery or "what-if" scenarios. + + + +**Important**: `MERGE_ENTITY_CACHE` _replaces_ the currently cached collections with the entity collections in its payload. +It does not _merge_ the payload collection entities into the existing collections as the name might imply. +May reconsider and do that in the future. + + + +If you want to create and reduce additional, cache-wide actions, +consider the _EntityCache MetaReducer_, described in the next section. + +## _MetaReducers_ + +The `NgRx/store` supports [**MetaReducers**](guide/store/metareducers) that can inspect and process actions flowing through the store and potentially change state in the store. + +A _MetaReducer_ is a function that takes a reducer and returns a reducer. +NgRx composes these reducers with other reducers in a chain of responsibility. + +NgRx calls the reducer returned by a MetaReducer just as it does any reducer. +It calls it with a _state_ object and an _action_. + +The MetaReducer can do what it wants with the state and action. +It can log the action, handle the action on its own, delegate to the incoming reducer, post-process the updated state, or all of the above. + + + +Remember that the actions themselves are immutable. Do not change the action! + + + +Like every reducer, the state passed to a MetaReducer's reducer is only +the section of the store that is within the reducer's scope. + +NgRx Data supports two levels of MetaReducer + +1. _EntityCache MetaReducer_, scoped to the entire entity cache +1. _EntityCollection MetaReducer_, scoped to a particular collection. + + + +### Entity Cache _MetaReducers_ + +The **EntityCache MetaReducer** helps you inspect and apply actions that affect the _entire entity cache_. +You might add custom actions and an _EntityCache MetaReducer_ to update several collections at the +same time. + +An _EntityCache MetaReducer_ reducer must satisfy three requirements: + +1. always returns the entire entity cache. +1. return synchronously (no waiting for server responses). +1. never mutate the original action; clone it to change it. + + + +We intend to explain how in a documentation update. +For now, see the NgRx Data `entity-data.module.spec.ts` for examples. + + + +### Entity Collection _MetaReducers_ + +An **entity collection _MetaReducer_** takes an _entity collection reducer_ as its reducer argument and +returns a new _entity collection reducer_. + +The new reducer receives the `EntityCollection` and `EntityAction` arguments that would have gone to the original reducer. + +It can do what it wants with those arguments, such as: + +- log the action, +- transform the action into a different action (for the same entity collection), +- call the original reducer, +- post-process the results from original reducer. + +The new entity collection reducer must satisfy three requirements: + +1. always returns an `EntityCollection` for the same entity. +1. return synchronously (no waiting for server responses). +1. never mutate the original action; clone it to change it. + +#### Compared to Store MetaReducers + +While the _entity collection MetaReducer_ is modeled on the NgRx Store `MetaReducer` ("_Store MetaReducer_"), it is crucially different in several respects. + +The _Store MetaReducer_ broadly targets _store reducers_. +It wraps _store reducers_, sees _all actions_, and can update _any state within its scope_. + +But a _Store MetaReducer_ neither see nor wrap an _entity collection reducer_. +These _entity collection reducers_ are internal to the _EntityCache Reducer_ that is registered with the NgRx Data feature. + +An _entity collection MetaReducer_ is narrowly focused on manipulation of a single, target _entity collection_. +It wraps _all entity collection reducers_. + +Note that it can't access other collections, the _entity cache_, or any other state in the store. +If you need a cross-collection _MetaReducer_, try the [EntityCache MetaReducer](#cache-meta-reducers) +described above. + +#### Provide Entity _MetaReducers_ to the factory + +Create one or more _entity collection MetaReducers_ and +add them to an array. + +Provide this array with the `ENTITY_COLLECTION_META_REDUCERS` injection token +where you import the `NgRxDataModule`. + +The `EntityCollectionReducerRegistry` injects it and composes the +array of _MetaReducers_ into a single _meta-MetaReducer_. +The earlier _MetaReducers_ wrap the later ones in the array. + +When the factory register an `EntityCollectionReducer`, including the reducers it creates, +it wraps that reducer in the _meta-MetaReducer_ before +adding it to its registry. + +All `EntityActions` dispatched to the store pass through this wrapper on their way in and out of the entity-specific reducers. + + + +We intend to explain how to create and provide _entity collection MetaReducers_ in a documentation update. +For now, see the `entity-reducer.spec.ts` for examples. + + + + +# EntityServices + +`EntityServices` is a facade over the NgRx Data services and the NgRx Data `EntityCache`. + +## Registry of _EntityCollectionServices_ + +It is primarily a registry of [EntityCollectionServices](guide/data/entity-collection-service). + +Call its `EntityServices.getEntityCollectionService(entityName)` method to get the singleton +`EntityCollectionService` for that entity type. + +Here's a component doing that. + + + +```ts +import { EntityCollectionService, EntityServices } from '@ngrx/data'; +import { Hero } from '../../model'; + +@Component({...}) +export class HeroesComponent implements OnInit { + heroesService: EntityCollectionService; + + constructor(entityServices: EntityServices) { + this.heroesService = entityServices.getEntityCollectionService('Hero'); + } +} +``` + + + +If the `EntityCollectionService` service does not yet exist, +`EntityServices` creates a default instance of that service and registers +that instance for future reference. + +## Create a custom _EntityCollectionService_ + +You'll often create custom `EntityCollectionService` classes with additional capabilities and convenience members, +as explained in the [EntityCollectionService](guide/data/entity-collection-service) doc. + +Here's an example. + + + +```ts +import { Injectable } from '@angular/core'; +import { + EntityCollectionServiceBase, + EntityCollectionServiceElementsFactory, +} from '@ngrx/data'; + +import { Hero } from '../model'; + +@Injectable() +export class HeroesService extends EntityCollectionServiceBase { + constructor( + elementsFactory: EntityCollectionServiceElementsFactory + ) { + super('Hero', elementsFactory); + } + + // ... your special sauce here +} +``` + + + +Of course you must provide the custom service before you use it, typically in an Angular `NgModule`. + + + +```ts +... +import { HeroesService } from './heroes.service'; + +@NgModule({ + imports: [...], + declarations: [...], + providers: [HeroesService] +}) +export class HeroesModule {} +``` + + + +The following alternative example uses the **preferred "tree-shakable" `Injectable()`** +to provide the service in the root module. + +```javascript +@Injectable({ providedIn: 'root' }) +export class HeroesService extends EntityCollectionServiceBase { + ... +} +``` + +You can inject that custom service directly into the component. + + + +```ts +@Component({...}) +export class HeroesComponent { + heroes$: Observable; + loading$: Observable; + + constructor(public heroesService: HeroesService) { + this.heroes$ = this.heroesService.entities$; + this.loading$ = this.heroesService.loading$; + } + ... +} +``` + + + +Nothing new so far. +But we want to be able to get the `HeroesService` from `EntityServices.getEntityCollectionService()` +just as we get the default collection services. + +This consistency will pay off when the app has a lot of collection services + +## Register the custom _EntityCollectionService_ + +When you register an instance of a custom `EntityCollectionService` with `EntityServices`, other callers of +`EntityServices.getEntityCollectionService()` get that custom service instance. + +You'll want to do that before anything tries to acquire it via the `EntityServices`. + +One solution is to inject custom collection services in the constructor of the module that provides them, +and register them there. + +The following example demonstrates. + + + +```ts +@NgModule({ ... }) +export class AppModule { + // Inject the service to ensure it registers with EntityServices + constructor( + entityServices: EntityServices, + // custom collection services + hs: HeroesService, + vs: VillainsService + ){ + entityServices.registerEntityCollectionServices([hs, vs]); + } +} +``` + + + +## Sub-class _EntityServices_ for application class convenience + +Another useful solution is to create a sub-class of `EntityServices` +that both injects the custom collection services +and adds convenience members for your application. + +The following `AppEntityServices` demonstrates. + + + +```ts +import { Injectable } from '@angular/core'; +import { + EntityServicesBase, + EntityServicesElements, +} from '@ngrx/data'; + +import { SideKick } from '../../model'; +import { HeroService, VillainService } from '../../services'; + +@Injectable() +export class AppEntityServices extends EntityServicesBase { + constructor( + elements: EntityServicesElements, + + // Inject custom services, register them with the EntityServices, and expose in API. + readonly heroesService: HeroesService, + readonly villainsService: VillainsService + ) { + super(elements); + this.registerEntityCollectionServices([ + heroesService, + villainsService, + ]); + } + + /** get the (default) SideKicks service */ + get sideKicksService() { + return this.getEntityCollectionService('SideKick'); + } +} +``` + + + +`AppEntityService` first injects the `EntityServicesElements` helper which it passes straight through to the base class constructor. +The "elements" enclose the ingredients that the base class needs to make and manage the entities you described in metadata. + +Then it injects your two custom collection services, `HeroesService` and `VillainsService`, +and exposes them directly to consumers as convenience properties for accessing those services. + +In this example, we don't need a custom collection service for the `SideKick` entity. +The default service will do. + +Nonetheless, we add a `sideKicksService` property that gets or creates a default service for `SideKick`. +Consumers will find this more discoverable and easier to call than `getEntityCollectionService()`. + +Of course the base class `EntityServices` members, such as `getEntityCollectionService()`, `entityCache$`, +and `registerEntityCollectionService()` are all available. + +Next, provide `AppEntityServices` in an Angular `NgModule` both as itself (`AppEntityServices`) +and as an alias for `EntityServices`. + +In this manner, an application class references this same `AppEntityServices` service instance, +whether it injects `AppEntityServices` or `EntityServices`. + +See it here in the sample app. + + + +```ts +@NgModule({ + imports: [ ... ], + providers: [ + AppEntityServices, + { provide: EntityServices, useExisting: AppEntityServices }, + ... + ] +}) +export class EntityStoreModule { ... } +``` + + + +## Access multiple _EntityCollectionServices_ + +A complex component may need access to multiple entity collections. +The `EntityServices` registry makes this easy, +even when the `EntityCollectionServices` are customized for each entity type. + +You'll only need **a single injected constructor parameter**, the `EntityServices`. + + + +```ts +import { EntityCollectionService, EntityServices } from '@ngrx/data'; +import { SideKick } from '../../model'; +import { HeroService, VillainService } from '../../services'; + +@Component({...}) +export class CharacterContainerComponent implements OnInit { + heroesService: HeroService; + sideKicksService: EntityCollectionService; + villainService: VillainService; + + heroes$: Observable; + ... + constructor(entityServices: EntityServices) { + this.heroesService = entityServices.getEntityCollectionService('Hero'); + this.sidekicksService = entityServices.getEntityCollectionService('SideKick'); + this.villainService = entityServices.getEntityCollectionService('Villain'); + + this.heroes$ = this.heroesService.entities$; + ... + } + ... +} +``` + + + +An application-specific sub-class of `EntityServices`, such as the `AppEntityServices` above, +makes this a little nicer. + + + +```ts +import { AppEntityServices } from '../../services'; + +@Component({...}) +export class CharacterContainerComponent implements OnInit { + + heroes$: Observable; + ... + constructor(private appEntityServices: AppEntityServices) { + this.heroes$ = appEntityServices.heroesService.entities$; + ... + } + ... +} +``` + + + + +# Extension Points + +**Work in progress** + +The `NgRx Data` library strives for the "_it just works_" experience. +But customizations are an inevitable necessity. + +The `NgRx Data` library invites you to customize its behavior at many points, +most of them listed here. + +## Take control of an entity type + +One day you decide that a particular entity type needs special treatment. +You want to take over some or all of the management of that type. + +You can do that easily without abandoning NgRx Data for the rest of your entity model. + +You can take it over completely simply by removing it from the entity metadata. +Create your own collection and add it to the store's state-tree as you would in vanilla NgRx. Create your own actions, reducers, selectors and effects. +As long as your actions don't have an `entityName` or `entityOp` property, +NgRx Data will ignore them. + +Or you can keep the entity type in the NgRx Data system and take over the behaviors that matter to you. + +- Create supplemental actions for that type. Give them custom `entityOp` names that suit your purpose. + +- Register an alternative `EntityCollectionReducer` for that type with the `EntityCollectionReducerFactory`. Your custom reducer can respond to your custom actions and implement the standard operations in its own way. + +- Create your own service facade, an alternative to `EntityCollectionService`, that dispatches the actions you care about + and exposes the selectors that your type needs. + +- Add additional properties to the collection state with the `EntityMetadata.additionalCollectionState` property. Manage these properties with custom reducer actions and selectors. + +- By-pass the `EntityEffects` completely by never dispatching an action with an `entityOp` that it intercepts. + Create a custom _NgRx/effect_ that handles your custom persistence actions. + +## Provide alternative service implementations + +The `NgRx Data` library consists of many services that perform small tasks. + +Look at the many providers in `NgRx Data.module.ts`. +Provide your own version of any `NgRx Data` service, as long as it conforms to the service API and implements the expected behavior. + +Be sure to test your alternatives. + +## Custom _EntityCollectionService_ + +## Extend the _EntityCollection_ + +## Custom _EntityActions_ + +### Rename the generated entity action _type_ + +The `EntityActionFactory.create()` method relies on the `formatActionType()` method to +produce the `Action.type` string. + +The default implementation concatenates the entity type name with the `EntityOp`. +For example, querying all heroes results in the entity type, `[Hero] NgRx Data/query-all`. + +If you don't like that approach you can replace the `formatActionType()` method with a generator that produces action type names that are more to your liking. +The NgRx Data library doesn't make decisions based on the `Action.type`. + +## Custom _EntityDispatcher_ + +### Change the default save strategy + +The dispatcher's `add()`, `update()`, `delete()` methods dispatch +_optimistic_ or _pessimistic_ save actions based on settings in the `EntityDispatcherOptions`. + +These options come from the `EntityDispatcherFactory` that creates the dispatcher. +This factory gets the options from the entity's metadata. +But where the metadata lack options, the factory relies on its `defaultDispatcherOptions`. + +You can set these default options directly by injecting the `EntityDispatcherFactory` +and re-setting `defaultDispatcherOptions` _before_ creating dispatchers +(or creating an `EntityCollectionService` which creates a dispatcher). + +## Custom _effects_ + +The NgRx Data library has one NgRx `Effect`, the `EntityEffects` class. + +This class detects entity persistence actions, performs the persistence operation with a +call to an `EntityDataService` and channels the HTTP response through a +`PersistenceResultHandler` which produces a persistence results observable that +goes back to the NgRx store. + +The `EntityEffects` class intercepts actions that have an `entityOp` property whose +value is one of the `persistOps`. Other actions are ignored by this effect. + +It tries to process any action with such an `entityOp` property by looking for a + +### Choose data service for the type + +The [_Entity DataService_](guide/data/entity-dataservice) describes the +default service, how to provide a data service for a specific entity type +or replace the default service entirely. + +### Replace the generic-type effects + +### Handle effect for a specific type + +### Replace handling of the results of a data service call + +### Replace the EntityEffects entirely + +## Custom _Reducers_ + +The [_Entity Reducer_ guide](guide/data/entity-reducer#customizing) explains how to +customize entity reducers. + +## Custom _Selectors_ + +### Introduction + +`@ngrx/data` has several built-in selectors that are defined in the [EntitySelectors](api/data/EntitySelectors) interface. These can be used outside of a component. + +Many apps use `@ngrx/data` in conjunction with @ngrx/store including manually written reducers, actions, and so on. `@ngrx/data` selectors can be used to combine @ngrx/data state with the state of the entire application. + +### Using EntitySelectorsFactory + +[EntitySelectorsFactory](api/data/EntitySelectorsFactory) exposes a `create` method that can be used to create selectors outside the context of a component, such as in a `reducers/index.ts` file. + +#### Example + +```ts +/* src/app/reducers/index.ts */ +import * as fromCat from './cat.reducer'; +import { Owner } from '~/app/models'; + +export const ownerSelectors = + new EntitySelectorsFactory().create('Owner'); + +export interface State { + cat: fromCat.State; +} + +export const reducers: ActionReducerMap = { + cat: fromCat.reducer, +}; + +export const selectCatState = (state: State) => state.cat; + +export const { selectAll: selectAllCats } = + fromCat.adapter.getSelectors(selectCatState); + +export const selectedCatsWithOwners = createSelector( + selectAllCats, + ownerSelectors.selectEntityMap, + (cats, ownerEntityMap) => + cats.map((c) => ({ + ...c, + owner: ownerEntityMap[c.owner], + })) +); +``` + +## Custom data service + +### Replace the generic-type data service + +### Replace the data service for a specific type + +## Custom HTTP resource URLs + +### Add plurals + +### Replace the Pluralizer + +### Replace the HttpUrlGenerator + +This example replaces the `DefaultHttpUrlGenerator` with a customized `HttpUrlGenerator` that pluralizes both collection resource and entity resource URLs. + +The implementation simply overrides `DefaultHttpUrlGenerator.getResourceUrls(string, string)`: + +```ts +import { Injectable } from '@angular/core'; +import { + DefaultHttpUrlGenerator, + HttpResourceUrls, + normalizeRoot, + Pluralizer, +} from '@ngrx/data'; + +@Injectable() +export class PluralHttpUrlGenerator extends DefaultHttpUrlGenerator { + constructor(private myPluralizer: Pluralizer) { + super(myPluralizer); + } + + protected getResourceUrls( + entityName: string, + root: string + ): HttpResourceUrls { + let resourceUrls = this.knownHttpResourceUrls[entityName]; + if (!resourceUrls) { + const nRoot = normalizeRoot(root); + const url = `${nRoot}/${this.myPluralizer.pluralize( + entityName + )}/`.toLowerCase(); + resourceUrls = { + entityResourceUrl: url, + collectionResourceUrl: url, + }; + this.registerHttpResourceUrls({ [entityName]: resourceUrls }); + } + return resourceUrls; + } +} +``` + +Override the `HttpUrlGenerator` provider in the root `AppModule` where `EntityDataModule.forRoot()` is imported: + +```ts +@NgModule({ + // ... + imports: [ + // ... + EntityDataModule.forRoot({}), + ], + providers: [ + // ... + { provide: HttpUrlGenerator, useClass: PluralHttpUrlGenerator }, + ], +}) +export class AppModule {} +``` + +To unit test the custom HTTP URL generator: + +```ts +import { PluralHttpUrlGenerator } from './plural-http-url-generator'; +import { DefaultPluralizer } from '@ngrx/data'; + +describe('PluralHttpUrlGenerator', () => { + let generator: PluralHttpUrlGenerator; + + beforeEach(() => { + generator = new PluralHttpUrlGenerator(new DefaultPluralizer([])); + }); + + it('should be created', () => { + expect(generator).toBeTruthy(); + }); + + it('should pluralize entity resource URLs', () => { + let url = generator.entityResource('bar', 'https://foo.com/api'); + expect(url).toBe('https://foo.com/api/bars/'); + }); + + it('should pluralize collection resource URLs', () => { + const url = generator.collectionResource( + 'bar', + 'https://foo.com/api' + ); + expect(url).toBe('https://foo.com/api/bars/'); + }); + + it('should cache results (needed for 100% branch coverage)', () => { + const url = generator.entityResource( + 'bar', + 'https://foo.com/api' + ); + const cachedUrl = generator.entityResource( + 'bar', + 'https://foo.com/api' + ); + expect(cachedUrl).toBe(url); + }); +}); +``` + +## Serialization with back-end + +The shape of the JSON data going over the wire to-and-from the server often +doesn't match the shape of the entity model(s) in the client application. +You may need _serialization/deserialization_ transformation functions +to map between the client entity data and the formats expected by the web APIs. + +There are no facilities for this within `NgRx Data` itself although +that is a [limitation](guide/data/limitations#serialization) we might address in a future version. + +One option in the interim is to write such serialization functions and +inject them into the `HttpClient` pipeline with [`HttpClient` interceptors](https://angular.dev/guide/http/interceptors). + + +# NgRx Data FAQs + + + +## You said I'd never write an action. But what if ... + +Hold on. We said "you _may never_ write an action, reducer, selector, or effect." + +That doesn’t mean you _won’t ever_. +In fact, a critical feature of NgRx Data is that you can add your own properties to collections, additional actions, reducer cases, selectors, etc. + +You aren't locked in to the way NgRx Data does things. +You can customize almost anything, both at the single entity-type level and for all entity types. + +But you ONLY do so when you want to do something unusual … and that, by definition, is not boilerplate. + + + +## What is an _entity_? + +An **_entity_** is an object with _long-lived data values_ that you read from and write to a database. + + + +Operations that access the database are called **_persistence_** operations. + + + +An _entity_ refers to some "thing" in the application domain, such as a customer. +Such things are unique even as their values change. Accordingly each entity has a unique **_primary key_**, also called its **_identifier_**. + +Each _entity_ object is an instance of an **_entity type_**. That type could be represented explicitly as a class or an interface. Or it could just be a bag of data. + +To manage entities with NgRx Data, you describe each entity type with [**_entity metadata_**](guide/data/entity-metadata). + +The application's **_entity model_** is the set of all entity types in your application that are described with _entity metadata_. + +In some definitions, the _entity type_ and _entity model_ describe both the data and the _logic_ that govern that data such as data integrity rules (e.g., validations) and behaviors (e.g., calculations). The _current version_ of NgRx Data library is unaware of entity logic beyond what is strictly necessary to persist entity data values. + + + +## Is NgRx Data the answer for everything? + +**_No!_** +The NgRx Data library is narrowly focused on a single objective: +to simplify management of [_entity data_](#entity). + +Entity data are a particularly opportune target for simplification +because they appear in high volume in many applications and +the sheer number of _similar-but-different_ NgRx code artifacts necessary to manage that data is daunting. + +Anything we can do to shrink entity management code and to leverage the commonalities across entity types will repay the effort handsomely. + +But _entity data_ is only _one_ kind of application data. + +Configuration data, user roles, the user's prior selections, the current state of screen layouts ... +these are all important and highly idiosyncratic data that can benefit from +custom coding with standard _NgRx_ techniques. + +Data streamed from remote sources such as trading systems, +mobile asset tracking systems, and IoT devices are not entity data +and may not be a good fit for the NgRx Data library. +They are still worth managing with _NgRx_. + +It bears repeating: the NgRx Data library is good for +querying, caching, and saving _entity data_ ... and that's it. + + + +## How does NgRx Data relate to other NgRx libraries? + +NgRx is a collection of libraries for writing Angular applications in a "reactive style" that combines the +**[redux pattern](#redux)** and tools with [RxJS Observables](#rxjs). + +`NgRx Data` builds upon three _NgRx_ libraries: +[Store](guide/store), +[Effects](guide/effects), and +[Entity](guide/entity). + + + +## How is NgRx _Data_ different from NgRx _Entity_? + +**The NgRx Data library _extends_ [Entity](guide/entity)**. + +The _Entity_ library provides the +core representation of a single _entity collection_ within an NgRx _Store_. +Its `EntityAdapter` defines common operations for querying and updating individual cached entity collections. + +The NgRx Data library leverages these capabilities while offering higher-level features including: + +- metadata-driven entity model. + +- actions, reducers, and selectors for all entity types in the model. + +- asynchronous fetch and save HTTP operations as NgRx _Effects_. + +- a reactive `EntityCollectionService` with a simple API that + encapsulates _NgRx_ interaction details. + +Nothing is hidden from you. +The store, the actions, the adapter, and the entity collections remain visible and directly accessible. + +The fixes and enhancements in future NgRx _Entity_ versions flow through NgRx Data to your application. + + + +## What is _redux_? + +[Redux](https://redux.js.org/) is an implementation of a pattern for managing application [state](#state) in a web client application. + +It is notable for: + +- Holding all _shared state_ as objects in a single, central _store_. + +- All objects in the store are [_immutable_](https://en.wikipedia.org/wiki/Immutable_object). + You never directly set any property of any object held in a redux store. + +- You update the store by _dispatching actions_ to the store. + +- An _action_ is like a message. It always has a _type_. It often has a _payload_ which is the data for that message. + +- Action instances are immutable. + +- Action instances are serializable (because the redux dev tools demand it and we should be able to persist them to local browser storage between user sessions). + +- All store values are immutable and serializable. + +- _actions_ sent to the store are processed by _reducers_. A reducer may update the store by replacing old objects in the store with new objects that have the updated state. + +- All _reducers_ are β€œpure” functions. + They have no side-effects. + +- The store publishes an _event_ when updated by a reducer. + +- Your application listens for store _events_; when it hears an event of interest, the app pulls the corresponding object(s) from the store. + +_NgRx_ is similar in almost all important respects. +It differs most significantly in replacing _events_ with _observables_. + +_NgRx_ relies on +[RxJS Observables](#rxjs) to listen for store events, select those that matter, and push the selected object(s) to your application. + + + +## What is _state_? + +_State_ is data. +Applications have several kinds of state including: + +- _application_ state is _session_ data that determine how your application works. Filter values and router configurations are examples of _application_ state. + +- _persistent_ state is "permanent" data that you store in a remote database. [Entities](#entity) are a prime example of _persistent_ state. + +- _shared_ state is data that are shared among application components and services. + +In _NgRx_, as in the redux pattern, all stored state is (or should be) _immutable_. +You never change the properties of objects in the store. +You replace them with new objects, created through a merge of the previous property values and new property values. + +Arrays are completely replaced when you add, remove, or replace any of their items. + + + +## What are _RxJS Observables_ + +[RxJS Observables](https://rxjs-dev.firebaseapp.com/) is a library for programming in a "reactive style". + +Many Angular APIs produce _RxJS Observables_ so programming "reactively" with _Observables_ is familiar to many Angular developers. Search the web for many helpful resources on _RxJS_. + + + +## What's wrong with code generation? + +Some folks try to conquer the "too much boilerplate" problem by generating the code. + +Adding the `Foo` entity type? Run a code generator to produce _actions_, _action-creators_, _reducers_, _effects_, _dispatchers_, and _selectors_ for `Foo`. +Run another one to produce the service that makes HTTP GET, PUT, POST, and DELETE calls for `Foo`. + +Maybe it generates canned tests for them too. + +Now you have ten (or more) new files for `Foo`. Multiply that by a 100 entity model and you have 1000 files. Cool! + +Except you're responsible for everyone of those files. Overtime you're bound to modify some of them to satisfy some peculiarity of the type. + +Then there is a bug fix or a new feature or a new way to generate some of these files. It's your job to upgrade them. Which ones did you change? Why? + + +# @ngrx/data + +NgRx Data is an extension that offers a gentle introduction to NgRx by simplifying management of **entity data** while reducing the amount of explicitness. + +## Introduction + +Many applications have substantial _domain models_ with 10s or 100s of entity types. + +Such applications typically create, retrieve, update, and delete entity data that are "persisted" in a database of some sort, hosted on a remote server. + +Developers who build these apps with the NgRx [Store](guide/store), [Effects](guide/effects), and [Entity](guide/entity) libraries alone tend to write a large number of _actions_, _action-creators_, _reducers_, _effects_, _dispatchers_, and _selectors_ as well as the HTTP GET, PUT, POST, and DELETE methods _for each entity type_. +There will be a lot of repetitive code to create, maintain, and test. +The more entities in your model, the bigger the challenge. + +With NgRx Data you can develop large entity models quickly with very little code +and without knowing much NgRx at all. +Yet all of NgRx remains accessible to you, when and if you want it. + +NgRx Data is an abstraction over the Store, Effects, and Entity that radically reduces +the amount of code you'll write. +As with any abstraction, while you gain simplicity, +you lose the explicitness of direct interaction with the supporting NgRx libraries. + +## Key Concepts + +#### NgRx Data + +- automates the creation of actions, reducers, effects, dispatchers, and selectors for each entity type. +- provides default HTTP GET, PUT, POST, and DELETE methods for each entity type. +- holds entity data as collections within a cache which is a slice of NgRx store state. +- supports optimistic and pessimistic save strategies +- enables transactional save of multiple entities of multiple types in the same request. +- makes reasonable default implementation choices +- offers numerous extension points for changing or augmenting those default behaviors. + +NgRx Data targets management of _persisted entity data_, like _Customers_ and _Orders_, that many apps query and save to remote storage. That's its sweet spot. + +It is ill-suited to _non-entity_ data. +Value types, enumerations, session data and highly idiosyncratic data are better managed with standard NgRx. +Real-world apps will benefit from a combination of NgRx techniques, all sharing a common store. + +#### Entity + +An **entity** is an object with long-lived data values that you read from and write to a database. An entity refers to some "thing" in the application domain. Examples include a _Customer_, _Order_, _LineItem_, _Product_, _Person_ and _User_. + +An **entity** is a specific kind of data, an object defined by its _thread of continuity and identity_. + +We experience its "continuity" by storing and retrieving ("persisting") entity objects in a permanent store on a server, a store such as a database. Whether we retrieve the "Sally" entity today or tomorrow or next week, we "mean" that we're getting the same conceptual "Sally" no matter how her data attributes have changed. + +In NgRx Data we maintain the entity object's identity by means of its **primary key**. Every entity in NgRx Data must have a _primary key_. The primary key is usually a single attribute of the object. For example, that "Sally" entity object might be an instance of the "Customer" entity type, an instance whose permanent, unchanging primary key is the `id` property with a value of `42`. + +The primary key doesn't have to be a single attribute. It can consist of multiple attributes of the object if you need that feature. What matters is that the primary key _uniquely_ identifies that object within a permanent collection of entities of the same type. There can be exactly one `Customer` entity with `id: 42` and that entity is "Sally". + +### Entity Collection + +The notion of an _Entity Collection_ is also fundamental to NgRx Data. All entities belong to a collection of the same entity type. A `Customer` entity belongs to a `Customers` collection. + +Even if you have only one instance of an entity type, it must be held within an entity collection: perhaps a collection with a single element. + +## Defining the entities + +A `EntityMetadataMap` tells NgRx Data about your entities. Add a property to the set for each entity name. + + + +```ts +import { EntityMetadataMap } from '@ngrx/data'; + +const entityMetadata: EntityMetadataMap = { + Hero: {}, + Villain: {}, +}; + +// because the plural of "hero" is not "heros" +const pluralNames = { Hero: 'Heroes' }; + +export const entityConfig = { + entityMetadata, + pluralNames, +}; +``` + + + +Export the entity configuration to be used when registering it in your `AppModule`. + +## Registering the entity store + +Once the entity configuration is created, you need to put it into the root store for NgRx. This is done by importing the `entityConfig` and then passing it to the `EntityDataModule.forRoot()` function. + + + +```ts +import { NgModule } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreModule } from '@ngrx/store'; +import { + DefaultDataServiceConfig, + EntityDataModule, +} from '@ngrx/data'; +import { entityConfig } from './entity-metadata'; + +@NgModule({ + imports: [ + HttpClientModule, + StoreModule.forRoot({}), + EffectsModule.forRoot([]), + EntityDataModule.forRoot(entityConfig), + ], +}) +export class AppModule {} +``` + + + +### Using the Standalone API + +Registering the root entity data can also be done using the standalone APIs if you are bootstrapping an Angular application using standalone features. + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideHttpClient } from '@angular/common/http'; +import { provideStore } from '@ngrx/store'; +import { provideEffects } from '@ngrx/effects'; +import { provideEntityData, withEffects } from '@ngrx/data'; + +import { AppComponent } from './app.component'; +import { entityConfig } from './entity-metadata'; + +bootstrapApplication(AppComponent, { + providers: [ + provideHttpClient(), + provideStore(), + provideEffects(), + provideEntityData(entityConfig, withEffects()), + ], +}); +``` + + + +## Creating entity data services + +NgRx Data handles creating, retrieving, updating, and deleting data on your server by extending `EntityCollectionServiceBase` in your service class. + + + +```ts +import { Injectable } from '@angular/core'; +import { + EntityCollectionServiceBase, + EntityCollectionServiceElementsFactory, +} from '@ngrx/data'; +import { Hero } from '../core'; + +@Injectable({ providedIn: 'root' }) +export class HeroService extends EntityCollectionServiceBase { + constructor( + serviceElementsFactory: EntityCollectionServiceElementsFactory + ) { + super('Hero', serviceElementsFactory); + } +} +``` + + + +## Using NgRx Data in components + +To access the entity data, components should inject entity data services. + + + +```ts +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Hero } from '../../core'; +import { HeroService } from '../hero.service'; + +@Component({ + selector: 'app-heroes', + templateUrl: './heroes.component.html', + styleUrls: ['./heroes.component.scss'], +}) +export class HeroesComponent implements OnInit { + loading$: Observable; + heroes$: Observable; + + constructor(private heroService: HeroService) { + this.heroes$ = heroService.entities$; + this.loading$ = heroService.loading$; + } + + ngOnInit() { + this.getHeroes(); + } + + add(hero: Hero) { + this.heroService.add(hero); + } + + delete(hero: Hero) { + this.heroService.delete(hero.id); + } + + getHeroes() { + this.heroService.getAll(); + } + + update(hero: Hero) { + this.heroService.update(hero); + } +} +``` + + + +In this example, you need to listen for the stream of heroes. The `heroes$` property references the `heroService.entities$` Observable. When state is changed as a result of a successful HTTP request (initiated by `getAll()`, for example), the `heroes$` property will emit the latest Hero array. + +By default, the service includes the `loading$` Observable to indicate whether an HTTP request is in progress. This helps applications manage loading states. + + +# Data Installation + +## Installing with `ng add` + +You can install the Data package to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/data@latest +``` + +### Optional `ng add` flags + +| flag | description | value type | default value | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `EntityDataModule` to. | `string` | +| `--module` | Name of file containing the module that you wish to add the import for the `EntityDataModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string` | `app` | +| `--effects` | If `false` it will use the `EntityDataModuleWithoutEffects` module instead of the default `EntityDataModule`. | `boolean` | `true` | +| `--migrateNgRxData` | If `true` it will replace the `ngrx-data` module with the `@ngrx/data` module. | `boolean` | `false` | +| `--entityConfig` | If `false` it will not create and declare the `entity-metadata` file. | `boolean` | `true` | + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/data`. +2. Run `npm install` to install those dependencies. +3. Update your `src/app/app.module.ts` > `imports` array with `EntityDataModule` or `EntityDataModuleWithoutEffects` depending on the `effects` flag. + +With the `migrateNgRxData` flag the following will also take place: + +1. Remove `ngrx-data` from `package.json` > `dependencies`. +2. Rename `ngrx-data` types to the matching `@ngrx/data` types. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/data --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/data +``` + + +# NgRx Data Limitations + +The NgRx Data library lacks many capabilities of a [full-featured entity management](#alternatives) system. + +You may be able to work-around some of the limitations without too much effort, +particularly when the shortcomings are a problem for just a few entity types. + +This page lists many of the serious limitations we've recognized ourselves. + +If curing them were easy, we'd have done so already. +Sometimes there are acceptable, well-known solutions that just take a little more effort. +Some solutions are too complicated or perform poorly. +In some cases, we have no good ideas at all. + +If there's enough interest in this NgRx Data, we'd like to tackle some of these problems. +We could use your help. + +## Deep entity cloning + +This library (like the NgRx [Entity](guide/entity) library +on which it depends) assumes that entity property values +are simple data types such as strings, numbers, and dates. + +Nothing enforces that assumption. +Many web APIs return entity data with complex properties. +A property value could be a _value type_ (e.g., a _money type_ that combines a currency indicator and an amount). +It could have a nested structure (e.g., an address). + +This library shallow-clones the entity data in the collections. +It doesn't clone complex, nested, or array properties. +You'll have to do the deep equality tests and cloning yourself _before_ asking NgRx Data to save data. + +## Non-normalized server responses + +Many query APIs return an entity bundle with data for many different entity types. + +This library only handles responses with a single entity or an array of entities of the same type. +When you adopt the _redux_ pattern, you're expected to "normalize" the entity data +as you would a _relational database_. + +This library lacks the tools to help you disaggregate and normalize server response data. + +## Entity relationships and navigation + +Entities are often related to each other via _foreign keys_. +These relationships can be represented as a directed graph, often with cycles. + +This library is unaware of _relationships_ and _foreign keys_ that may be implicit in the entity data. +It's up to you to make something out of those relationships and keys. + +It's not easy to represent relationships. + +A `Customer` entity could have a one-to-many relationship with `Order` entities. +The `Order` entity has an `order.customer` property whose value is the primary key +of its parent `Customer`. + +Each `Order` has related `LineItems`. +A `LineItems` has a one-to-one relationship with `Product`. +And so it goes. + +There are other cardinalities to consider (one-to-zero, one-to-zero-or-many, many-to-many, etc.). +A good solution would include an extension of the `EntityMetadata` that identified relationships, their cardinalities, and their foreign keys. + +It can be convenient to construct classes for `Customer` and `Order` that have +properties for navigating between them (_navigation properties_). +The domain logic for the model may argue for unidirectional navigations in some cases +and bi-directional navigations in others. + +We have to be prepared for any load order. +The _orders_ could arrive before their parent _customers_. +A good solution would tolerate that, making connections and breaking them again +as entities enter and leave the cache. + +There will be long chains of navigations (`Customer <-> Order <-> <-> LineItem <-> Product <-> Supplier`). +How should these be implemented? + +One approach is to combine _Observable selector_ properties like this + +```typescript +orders$ = combineLatest([currentCustomerId$, orders$]).pipe( + map(([customerId, orders]) => + orders.filter((o) => o.customerId === customerId) + ) +); +``` + + + +We'll explore this and rival approaches in a future documentation update. + + + +## Client-side primary key generation + +You are responsible for setting the primary key of an entity you create. + +If the server supplies the key, you can send the new entity to the server +and rely on the server to send the entity back with its assigned key. +It's up to you to orchestrate that cycle. + +It's far better if the client assigns the key. +You can create new records offline or recover if your connection to the server +breaks inconveniently during the save. + +It's easy to generate a new _guid_ (or _uuid_) key. +It's much harder to generate integer or semantic keys because +you need a foolproof way to enforce uniqueness. + +Server-supplied keys greatly complicate maintenance of a cache of inter-related entities. +You'll have to find a way to hold the related entities together until you can save them. + +Temporary-key generation is one approach. It requires complex key-fixup logic +to replace the temporary keys in _foreign key properties_ +with the server-supplied permanent keys. + +## Data integrity rules + +Entities are often governed by intra- and inter-entity validation rules. +The `Customer.name` property may be required. +The `Order.shipDate` must be after the `Order.orderDate`. +The parent `Order` of a `LineItem` may have to exist. + +You can weave validation rules into your application logic +but you'll have to do so without the help of the `NgRx Data` library. + +It would be great if the library knew about the rules (in `EntityMetadata`?), ran the validation rules at appropriate times, displayed validation errors on screen, and prevented the save of entities with errors. + +These might be features in a future version of this library. + + + +## Server/client entity mapping + +The representation of an entity on the server may be different than on the client. + +Perhaps the camelCased property names on the client-side entity are PascalCased on the server. +Maybe a server-side property is spelled differently than on the client. +Maybe the client entity should have some properties that don't belong on the server entity (or vice-versa). + +Today you could transform the data in both directions with [`HttpClient` interceptors](https://angular.dev/guide/http/interceptors). +But this seems like a problem that would be more easily and transparently addressed as a feature of `NgRx Data`. + +## No request concurrency checking + +The user saves a new `Customer`, followed by a query for all customers. +Is the new customer in the query response? + +`NgRx Data` does not coordinate save and query requests and does not guarantee order of responses. + +You'll have to manage that yourself. +Here's some pseudo-code that might do that for the previous example: + +```javascript +// add new customer, then query all customers +customerService + .addEntity(newCustomer) + .pipe(concatMap(() => customerService.queryAll())) + .subscribe((custs) => (this.customers = custs)); +``` + +The same reasoning applies to _any_ request that must follow in a precise sequence. + +## No update concurrency checking + +There is no intrinsic mechanism to enforce concurrency checks when updating a record even if the record contains a concurrency property. + +For example, the user saves a change to the customer's address from "123 Main Street" to "45 Elm Avenue". +Then the user changes and saves the address again to "89 Bower Road". +Another user changes the same address to "67 Maiden Lane". + +What's the actual address in the database? What's the address in the user's cache? + +It could be any of the three addresses depending on when the server saw them and when the responses arrived. +You cannot know. + +Many applications maintain a concurrency property that guards against updating an entity +that was updated by someone else. +The `NgRx Data` library is unaware of this protocol. +You'll have to manage concurrency yourself. + +## No offline capability + +NgRx Data lacks support for accumulating changes while the application is offline and then saving those changes to the server when connectivity is restored. + +The _NgRx_ system has some of the ingredients of an offline capability. +Actions are immutable and serializable so they can be stashed in browser storage of some kind while offline and replayed later. + +But there are far more difficult problems to overcome than just recording changes for playback. +NgRx Data makes no attempt to address these problems. + +## Query language + +Servers often offer a sophisticated query API for selecting entities from the server, sorting them on the server, grabbing related entities at the same time, and reducing the number of downloaded fields. + +This library's `getWithQuery()` command takes a query specification in the form of a _parameter/value_ map or a URL query string. + +There is no apparatus for composing queries or sending them to the server except as a query string. + + + +## An alternative to NgRx Data + +[BreezeJS](http://www.getbreezenow.com/breezejs) is a free, open source, +full-featured entity management library that overcomes (almost) all of the +limitations described above. +Many Angular (and AngularJS) applications use _Breeze_ today. + +It's not the library for you if you **_require_** a small library that adheres to _reactive_, _immutable_, _redux-like_ principles. + + + +Disclosure: one of the NgRx Data authors, Ward Bell, is an original core Breeze contributor. + + + + +# Saving Multiple Entities + +Many apps must save several entities at the same time in the same transaction. + +Multiple entity saves are a first class feature. +By "first class" we mean that NgRx Data offers a built-in, multiple entity save solution that +is consistent with NgRx Data itself: + +- defines a `ChangeSet`, describing `ChangeOperations` to be performed on multiple entities of multiple types. +- has a set of `SAVE_ENTITIES...` cache-level actions. +- has an `EntityCacheDispatcher` to dispatch those actions. +- offers `EntityCacheEffects` that sends `SAVE_ENTITIES` async requests to the server and + returns results as `SAVE_ENTITIES_SUCCESS` or `SAVE_ENTITIES_ERROR` actions. +- offers a default `EntityCacheDataService` to make those http server requests. +- integrates with change tracking. +- delegates each collection-level change to the (customizable) `entity-collection-reducer-methods`. + + + +You could implement multiple-entity saves yourself by, prior to version 6.1. +You could define your own protocol and manipulate the `EntityCache` directly by dispatching `SET_ENTITY_CACHE` +after updating a copy of the current cache before and after save. +The collection-level reducers in `entity-collection-reducer-methods` and the NgRx `EntityAdapters` would help. + +It wouldn't be easy and there are many steps that can be easily overlooked. But you could do it. + + + +### Save with _EntityCacheDispatcher.saveEntities()_ + +This NgRx Data version includes a new `EntityCacheDispatcher` whose +methods make it easier to create and dispatch all of the entity cache actions. + +Save a bunch of entity changes with the `saveEntities()` dispatcher method. +Call it with a URL and a `ChangeSet` describing the entity changes that the server API (at the URL endpoint) should save. + +The sample application demonstrates a simple `saveEntities` scenario. +A button on the _Villains_ page deletes all of the villains. + +In the following example, we want to add a `Hero` and delete two `Villains` in the same transaction. +We assume a server is ready to handle such a request. + +First create the changes (each a `ChangeSetItem`) for the `ChangeSet`. + + + +```ts +import { ChangeSetOperation } from '@ngrx/data'; +... +const changes: ChangeSetItem[] = [ + { + op: ChangeSetOperation.Add, + entityName: 'Hero', + entities: [hero] + }, + { + op: ChangeSetOperation.Delete, + entityName: 'Villain', + entities: [2, 3] // delete by their ids + } +]; +``` + + + +The `changeSetItemFactory` makes it easier to write these changes. + +```typescript +import { changeSetItemFactory as cif } from '@ngrx/data'; +... +const changes: ChangeSetItem[] = [ + cif.add('Hero', hero), + cif.delete('Villain', [2, 3]) +]; +``` + +Now dispatch a `saveEntities` with a `ChangeSet` for those changes. + +```typescript +const changeSet: ChangeSet = { changes, tag: 'Hello World' }; + +cacheEntityDispatcher + .saveEntities(changeSet, saveUrl) + .subscribe((result) => log('Saved ChangeSet')); +``` + +The `saveEntities(changeSet, saveUrl)` returns an `Observable`, +which emits a new `ChangeSet` after the server API (at the `saveUrl` endpoint) returns a successful response. + +That emitted `ChangeSet` holds the server's response data for all affected entities. + +The app can wait for the `saveEntities()` observable to terminate (either successfully or with an error), before proceeding (e.g., routing to another page). + +#### How it works + +Internally, the method creates a `SAVE_ENTITIES` action whose payload data includes the `ChangeSet`. +The action also has the URL to which the requested save should be sent and a `correlationId` (see below). + +The method dispatches this action to the NgRx store where it is processed by the `EntityCacheReducer`. +If the action is "optimistic", the reducer updates the cache with changes immediately. + +Then the `EntityCacheEffects` picks up the `SAVE_ENTITIES` action and sends a "save changes" request to +the server's API endpoint (the URL). + +If the request succeeds, the server returns data for all of the changed (and deleted) entities. +The `EntityCacheEffects` packages that data into a `SAVE_ENTITIES_SUCCESS` action and dispatches it to the store. + +The `EntityCacheReducer` for the `SAVE_ENTITIES_SUCCESS` action +updates the cache with the (possibly altered) entity data from the server. + +Meanwhile, the `Observable` from the `saveEntities()` dispatcher method is +watching the stream of actions dispatched to the store. +When a `SAVE_ENTITIES_SUCCESS` (or `SAVE_ENTITIES_ERROR`) action emerges and +it has the same `correlationId` as the original `SAVE_ENTITIES` action, +the observable emits the `ChangeSet` (or error). + +The subscriber to that observable now knows that this particular _save entities_ request is "done". + + + +This complicated dance is standard NgRx. Fortunately, all you have to know is that you can call `saveEntities()` with the `ChangeSet` and URL, then wait for the returned observable to emit. + + + +#### _ChangeSet_ + +The `ChangeSet` interface is a simple structure with only one critical property, +`changes`, which holds the entity data to save. + + + +```ts +export interface ChangeSet { + /** An array of ChangeSetItems to be processed in the array order */ + changes: ChangeSetItem[]; + + /** + * An arbitrary, serializable object that should travel with the ChangeSet. + * Meaningful to the ChangeSet producer and consumer. Ignored by NgRx Data. + */ + extras?: T; + + /** An arbitrary string, identifying the ChangeSet and perhaps its purpose */ + tag?: string; +} +``` + + + +At the heart of it is `changes`, an array of `ChangeSetItems` that describes a change operation to be performed with one or more entities of a particular type. + +For example, + +- a `ChangeSetAdd` could add 3 new `Hero` entities to the server's `Hero` collection. +- a `ChangeSetUpdate` could update 2 existing `Villain` entities. +- a `ChangeSetDelete` could delete a `SideKick` entity by its primary key. +- a `ChangeSetUpsert` could add two new `SuperPower` entities and update a third `SuperPower` entity. + +There are four `ChangeSetOperations` + + + +```ts +export enum ChangeSetOperation { + Add = 'Add', + Delete = 'Delete', + Update = 'Update', + Upsert = 'Upsert', +} +``` + + + + + +`Upsert` is a request to treat the entities in the `ChangeSetItem` as _either_ new entities or updates to _existing_ entities. + + + +Each kind of `ChangeSetItem` follows a pattern similar to `ChangeSetAdd`. + + + +```ts +export interface ChangeSetAdd { + op: ChangeSetOperation.Add; + entityName: string; + entities: T[]; +} +``` + + + +The `ChangeSetItem` flavors all have `op`, `entityName` and `entities` properties. +They differ substantively only in the nature of the `entities` array which corresponds to the change operation: + +- Add: entities +- Delete: primary keys of the entities to delete +- Update: NgRx Entity `Update`s +- Upsert: entities + +#### Pessimistic / Optimistic save + +The `EntityCacheDispatcher.saveEntities` dispatches the `SAVE_ENTITIES` action (with its `ChangeSet`) to the store where it is processed by the `EntityCacheReducer`. + +If the action is "pessimistic", the reducer sets the collection `loading` flags but doesn't update the entities in cache. +The reducer for the `SAVE_ENTITIES_SUCCESS` action, whose payload holds the successfully saved entities, will update the cached entities. + +If the action is "optimistic", the reducer applies the changes to the cache immediately, before you send them to the server. + +You can specify "optimistic" or "pessimistic" in the `options` parameter. +If you don't specify this option, NgRx Data uses the default value in +`EntityDispatcherDefaultOptions.optimisticSaveEntities`. +It is `false` (pessimistic) by default. + +#### Specify your own defaults + +You can provide alternative defaults. + +```typescript + { + provide: EntityDispatcherDefaultOptions, + useValue: myDispatcherDefaultOptions +} +``` + +#### Server + +The server API (the usual recipient of a `ChangeSet`) must be able to process the request. +NgRx Data doesn't know if the API can or cannot process a `ChangeSet` (and that includes whether the server can or cannot handle upserts). + +As always, make sure only to send something that the server API can handle. + +#### EntityCacheEffects + +You can handle the async HTTP _save changes_ request yourself, making your own calls to the server in your own way. + +Your solution can use the `EntityCacheDispacher` to dispatch `SAVE_ENTITIES`, `SAVE_ENTITIES_SUCCESS` and `SAVE_ENTITIES_ERROR` actions for updating the cache and managing the `ChangeState` of the entities in the `ChangeSet`. + +Perhaps better, you can let the `EntityCacheEffects` handle this for you in a manner similar to the v6 `EntityEffects` for single-entity saves. + +The `EntityCacheEffects.saveEntities$` effect listens for `SAVE_ENTITIES` and makes a request to the designated URL via the (new) `EntityCacheDataService`. +It takes the response and dispatches either a `SAVE_ENTITIES_SUCCESS` or `SAVE_ENTITIES_ERROR`, as appropriate. + + + +If you prefer to handle server interaction yourself, +you can disable the `EntityCacheEffects` by providing a null implementation, in your `NgModule`, e.g., + +```typescript +{ + provide: EntityCacheEffects: useValue: { + } +} +``` + + + +#### EntityCacheDataService + +The `EntityCacheDataService` constructs and POSTS the actual request to the given API URL. + +We anticipate that most server API implementors will not support the NgRx Entity `Update` structure within the `ChangeSet`. +So the `EntityCacheDataService.saveEntities()` method +extracts the `changes` from the `Updates[]` and sends these to the server; it then reconstructs the `Updates[]` entities in from the server response so that the NgRx Data consumer of the response sees those `Update` structures. + +As always, you can provide an alternative implementation: + +```typescript +{ + provide: EntityCacheDataService: useClass: MyCacheDataService; +} +``` + +#### Updating the store with server response data + +If the save was pessimistic, the EntityCache is unchanged until the server responds. +You need the results from the server to update the cache. + + + +The changes are already in cache with an optimistic save. +But the server might have made additional changes to the data, +in which case you'd want to (re)apply the server response data to cache. + + + +The server API is supposed to return all changed entity data in the +form of a `ChangeSet`. + +Often the server processes the saved entities without changing them. +There's no real need for the server to return the data. +The original request `ChangeSet` has all the information necessary to update the cache. +Responding with a `"204-No Content"` instead would save time, bandwidth, and processing. + +The server can respond `"204-No Content"` and send back nothing. +The `EntityCacheEffects` recognizes this condition and +returns a success action _derived_ from the original request `ChangeSet`. + +If the save was pessimistic, it returns a `SaveEntitiesSuccess` action with the original `ChangeSet` in the payload. + +If the save was optimistic, the changes are already in the cache and there's no point in updating the cache. +Instead, the effect returns a merge observable that clears the loading flags +for each entity type in the original `CacheSet`. + +#### New _EntityOPs_ for multiple entity save + +When the server responds with a `ChangeSet`, or the effect re-uses the original request `ChangeSet`, the effect returns a `SAVE_ENTITIES_SUCCESS` action with the `ChangeSet` in the payload. + +This `ChangeSet` has the same structure as the one in the `SAVE_ENTITIES` action, which was the source of the HTTP request. + +The `EntityCacheReducer` converts the `ChangeSet.changes` into +a sequence of `EntityActions` to the entity collection reducers. + +The `store` never sees these reducer calls (and you won't see them in the redux tools). +They are applied synchronously, in succession to an instance of the `EntityCache` object. + +After all `ChangeSet.changes` have been reduced, the `EntityCacheReducer` returns the updated `EntityCache` and the NgRx `Store` gets the new, fully-updated cache in one shot. + +That should mean that the cache is in a stable state, with all relationships updated, before any code outside the store hears of the changes. + +At that point, all affected entity `selectors$` will emit. + +#### New _EntityOPs_ for multiple entity save + +As always, the entity collection reducers know what to do based on the `EntityAction.entityOp`. + +Before v6.1, the _save_ `EntityOps` only worked for single entities. +This version adds multi-entity save actions to `EntityOp`: +`SAVE_ADD_MANY...`,`SAVE_DELETE_MANY...`, `SAVE_UPDATE_MANY...`,`SAVE_UPSERT_MANY...`. + + + +These ops do not have corresponding `EntityCommands` because a multi-entity save is dispatched (via `SAVE_ENTITIES..` actions) to the `EntityCache` reducer, +not to a collection reducer (at least not in this version). + + + +#### Transactions + +It is up to the server to process the `ChangeSet` as a transaction. +That's easy if the server-side store is a relational database. + +If your store doesn't support transactions, you'll have to decide if the multiple-entity save facility is right for you. + +On the NgRx Data client, it is "transactional" in the sense that a successful result returned by the server will be applied to the cache all at once. +If the server returns an error result, the cache is not touched. + +**_Important_**: if you saved "optimisitically", NgRx Data updates the cache _before_ sending the request to the server. + +NgRx Data _does not roll back_ the `EntityCache` automatically when an _optimistic save_ fails. + +Fortunately, the NgRx Data collection reducers updated the `ChangeState` of the affected entities _before merging_ the changes into the cache (see the NgRx Data `ChangeTracker`). + +You have good options if the save fails. + +- You _could_ rollback using the `ChangeTracker`. +- You could try again. +- You could fail the app. + +Let your failure analysis and application business rules guide your decision. + +#### Cancellation + +You can try to cancel a save by dispatching the `SAVE_ENTITIES_CANCEL` action with the +**correlation id** of the _save action_ that you want to cancel. + +An optional `EntityNames` array argument tells the `EntityCache` reducer to turn off the `loading` flags +for the collections named in that array (these flags would have been turned on by `SAVE_ENTITIES`). +You can also supply a cancellation "reason" and the usual action tag. + +The `EntityCacheEffects.saveEntitiesCancel$` watches for this action and is piped into +the `EntityCacheEffects.saveEntities$`, where it can try to cancel the save operation +or at least prevent the server response from updating the cache. + + + +It's not obvious that this is ever a great idea. +You cannot tell the server to cancel this way and cannot know if the server did or did not save. +Nor can you count on processing the cancel request before the client receives the server response +and applies the changes on the server or to the cache. + +If you cancel before the server results arrive, the `EntityCacheEffect` will not try to update +the cache with late arriving server results. +The effect will issue a `SAVE_ENTITIES_CANCELED` action instead. +The `EntityCache` reducer ignores this action but you can listen for it among the store actions +and thus know that the cancellation took effect on the client. + + + + +# @ngrx/effects + +Effects are an RxJS powered side effect model for [Store](guide/store). Effects use streams to provide [new sources](https://martinfowler.com/eaaDev/EventSourcing.html) of actions to reduce state based on external interactions such as network requests, web socket messages and time-based events. + +## Introduction + +In a service-based Angular application, components are responsible for interacting with external resources directly through services. Instead, effects provide a way to interact with those services and isolate them from the components. Effects are where you handle tasks such as fetching data, long-running tasks that produce multiple events, and other external interactions where your components don't need explicit knowledge of these interactions. + +## Key Concepts + +- Effects isolate side effects from components, allowing for more _pure_ components that select state and dispatch actions. +- Effects are long-running services that listen to an observable of _every_ action dispatched from the [Store](guide/store). +- Effects filter those actions based on the type of action they are interested in. This is done by using an operator. +- Effects perform tasks, which are synchronous or asynchronous and return a new action. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/effects/install) page. + +## Comparison with Component-Based Side Effects + +In a service-based application, your components interact with data through many different services that expose data through properties and methods. These services may depend on other services that manage other sets of data. Your components consume these services to perform tasks, giving your components many responsibilities. + +Imagine that your application manages movies. Here is a component that fetches and displays a list of movies. + + + +```ts +import { Component, inject, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + template: ` +
  • + {{ movie.name }} +
  • + `, + imports: [CommonModule], +}) +export class MoviesPageComponent implements OnInit { + private moviesService = inject(MoviesService); + protected movies: Movie[] = []; + + ngOnInit() { + this.movieService + .getAll() + .subscribe((movies) => (this.movies = movies)); + } +} +``` + +
    + +You also have the corresponding service that handles the fetching of movies. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class MoviesService { + private http = inject(HttpClient); + + getAll(): Observable { + return this.http.get('/movies'); + } +} +``` + + + +The component has multiple responsibilities: + +- Managing the _state_ of the movies. +- Using the service to perform a _side effect_, reaching out to an external API to fetch the movies. +- Changing the _state_ of the movies within the component. + +`Effects` when used along with `Store`, decrease the responsibility of the component. In a larger application, this becomes more important because you have multiple sources of data, with multiple services required to fetch those pieces of data, and services potentially relying on other services. + +Effects handle external data and interactions, allowing your services to be less stateful and only perform tasks related to external interactions. Next, refactor the component to put the shared movie data in the `Store`. Effects handle the fetching of movie data. + + + +```ts +import { Component, inject, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + template: ` +
    + {{ movie.name }} +
    + `, + imports: [CommonModule], +}) +export class MoviesPageComponent implements OnInit { + private store = inject(Store<{ movies: Movie[] }>); + protected movies$ = this.store.select((state) => state.movies); + + ngOnInit() { + this.store.dispatch({ type: '[Movies Page] Load Movies' }); + } +} +``` + +
    + +The movies are still fetched through the `MoviesService`, but the component is no longer concerned with how the movies are fetched and loaded. It's only responsible for declaring its _intent_ to load movies and using selectors to access movie list data. Effects are where the asynchronous activity of fetching movies happens. Your component becomes easier to test and less responsible for the data it needs. + +## Writing Effects + +To isolate side effects from your component, you can create NgRx effects to listen for events and perform tasks. + +Effects are injectable service classes with distinct parts: + +- An injectable `Actions` service that provides an observable stream of _each_ action dispatched _after_ the latest state has been reduced. +- Metadata is attached to the observable streams using the `createEffect` function. The metadata is used to register the streams that are subscribed to the store. Any action returned from the effect stream is then dispatched back to the `Store`. +- Actions are filtered using a pipeable [`ofType` operator](guide/effects/operators#oftype). The `ofType` operator takes one or more action types as arguments to filter on which actions to act upon. +- Effects are subscribed to the `Store` observable. +- Services are injected into effects to interact with external APIs and handle streams. + + + +**Note:** Since NgRx v15.2, classes are not required to create effects. Learn more about functional effects [here](#functional-effects). + + + +To show how you handle loading movies from the example above, let's look at `MoviesEffects`. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { EMPTY } from 'rxjs'; +import { map, exhaustMap, catchError } from 'rxjs/operators'; +import { MoviesService } from './movies.service'; + +@Injectable() +export class MoviesEffects { + private actions$ = inject(Actions); + private moviesService = inject(MoviesService); + + loadMovies$ = createEffect(() => { + return this.actions$.pipe( + ofType('[Movies Page] Load Movies'), + exhaustMap(() => + this.moviesService.getAll().pipe( + map((movies) => ({ + type: '[Movies API] Movies Loaded Success', + payload: movies, + })), + catchError(() => EMPTY) + ) + ) + ); + }); +} +``` + + + +The `loadMovies$` effect is listening for all dispatched actions through the `Actions` stream, but is only interested in the `[Movies Page] Load Movies` event using the `ofType` operator. The stream of actions is then flattened and mapped into a new observable using the `exhaustMap` operator. The `MoviesService#getAll()` method returns an observable that maps the movies to a new action on success, and currently returns an empty observable if an error occurs. The action is dispatched to the `Store` where it can be handled by reducers when a state change is needed. It's also important to [handle errors](#handling-errors) when dealing with observable streams so that the effects continue running. + + + +**Note:** Event streams are not limited to dispatched actions, but can be _any_ observable that produces new actions, such as observables from the Angular Router, observables created from browser events, and other observable streams. + + + +## Handling Errors + +Effects are built on top of observable streams provided by RxJS. Effects are listeners of observable streams that continue until an error or completion occurs. In order for effects to continue running in the event of an error in the observable, or completion of the observable stream, they must be nested within a "flattening" operator, such as `mergeMap`, `concatMap`, `exhaustMap`, and `switchMap`. The example below shows the `loadMovies$` effect handling errors when fetching movies. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { map, exhaustMap, catchError } from 'rxjs/operators'; +import { MoviesService } from './movies.service'; + +@Injectable() +export class MoviesEffects { + private actions$ = inject(Actions); + private moviesService = inject(MoviesService); + + loadMovies$ = createEffect(() => { + return this.actions$.pipe( + ofType('[Movies Page] Load Movies'), + exhaustMap(() => + this.moviesService.getAll().pipe( + map((movies) => ({ + type: '[Movies API] Movies Loaded Success', + payload: movies, + })), + catchError(() => + of({ type: '[Movies API] Movies Loaded Error' }) + ) + ) + ) + ); + }); +} +``` + + + +The `loadMovies$` effect returns a new observable in case an error occurs while fetching movies. The inner observable handles any errors or completions and returns a new observable so that the outer stream does not die. You still use the `catchError` operator to handle error events, but return an observable of a new action that is dispatched to the `Store`. + +## Functional Effects + +Functional effects are also created by using the `createEffect` function. They provide the ability to create effects outside the effect classes. + +To create a functional effect, add the `functional: true` flag to the effect config. Then, to inject services into the effect, use the [`inject` function](https://angular.dev/api/core/inject). + + + +```ts +import { inject } from '@angular/core'; +import { catchError, exhaustMap, map, of, tap } from 'rxjs'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; + +import { ActorsService } from './actors.service'; +import { ActorsPageActions } from './actors-page.actions'; +import { ActorsApiActions } from './actors-api.actions'; + +export const loadActors = createEffect( + ( + actions$ = inject(Actions), + actorsService = inject(ActorsService) + ) => { + return actions$.pipe( + ofType(ActorsPageActions.opened), + exhaustMap(() => + actorsService.getAll().pipe( + map((actors) => + ActorsApiActions.actorsLoadedSuccess({ actors }) + ), + catchError((error: { message: string }) => + of( + ActorsApiActions.actorsLoadedFailure({ + errorMsg: error.message, + }) + ) + ) + ) + ) + ); + }, + { functional: true } +); + +export const displayErrorAlert = createEffect( + () => { + return inject(Actions).pipe( + ofType(ActorsApiActions.actorsLoadedFailure), + tap(({ errorMsg }) => alert(errorMsg)) + ); + }, + { functional: true, dispatch: false } +); +``` + + + + + +It's recommended to inject all dependencies as effect function arguments for easier testing. However, it's also possible to inject dependencies in the effect function body. In that case, the [`inject` function](https://angular.dev/api/core/inject) must be called within the synchronous context. + + + +## Registering Effects + +Effect classes and functional effects are registered using the `provideEffects` method. + +At the root level, effects are registered in the `providers` array of the application configuration. + + + +Effects start running **immediately** after instantiation to ensure they are listening for all relevant actions as soon as possible. +Services used in root-level effects are **not** recommended to be used with services that are used with the `APP_INITIALIZER` token. + + + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore } from '@ngrx/store'; +import { provideEffects } from '@ngrx/effects'; + +import { AppComponent } from './app.component'; +import { MoviesEffects } from './effects/movies.effects'; +import * as actorsEffects from './effects/actors.effects'; + +bootstrapApplication(AppComponent, { + providers: [ + provideStore(), + provideEffects(MoviesEffects, actorsEffects), + ], +}); +``` + + + +Feature-level effects are registered in the `providers` array of the route config. +The same `provideEffects()` method is used to register effects for a feature. + + + +Registering an effects class multiple times (for example in different lazy loaded features) does not cause the effects to run multiple times. + + + + + +```ts +import { Route } from '@angular/router'; +import { provideEffects } from '@ngrx/effects'; + +import { MoviesEffects } from './effects/movies.effects'; +import * as actorsEffects from './effects/actors.effects'; + +export const routes: Route[] = [ + { + path: 'movies', + providers: [provideEffects(MoviesEffects, actorsEffects)], + }, +]; +``` + + + +### Alternative Way of Registering Effects + +You can provide root-/feature-level effects with the provider `USER_PROVIDED_EFFECTS`. + + + +```ts +providers: [ + MoviesEffects, + { + provide: USER_PROVIDED_EFFECTS, + multi: true, + useValue: [MoviesEffects], + }, +]; +``` + + + +## Incorporating State + +If additional metadata is needed to perform an effect besides the initiating action's `type`, we should rely on passed metadata from an action creator's `props` method. + +Let's look at an example of an action initiating a login request using an effect with additional passed metadata: + + + +```ts +import { createAction, props } from '@ngrx/store'; +import { Credentials } from '../models/user'; + +export const login = createAction( + '[Login Page] Login', + props<{ credentials: Credentials }>() +); +``` + + + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, ofType, createEffect } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { catchError, exhaustMap, map } from 'rxjs/operators'; +import { LoginPageActions, AuthApiActions } from '../actions'; +import { Credentials } from '../models/user'; +import { AuthService } from '../services/auth.service'; + +@Injectable() +export class AuthEffects { + private actions$ = inject(Actions); + private authService = inject(AuthService); + + login$ = createEffect(() => { + return this.actions$.pipe( + ofType(LoginPageActions.login), + exhaustMap((action) => + this.authService.login(action.credentials).pipe( + map((user) => AuthApiActions.loginSuccess({ user })), + catchError((error) => + of(AuthApiActions.loginFailure({ error })) + ) + ) + ) + ); + }); +} +``` + + + +The `login` action has additional `credentials` metadata which is passed to a service to log the specific user into the application. + +However, there may be cases when the required metadata is only accessible from state. When state is needed, the RxJS `withLatestFrom` or the @ngrx/effects `concatLatestFrom` operators can be used to provide it. + +The example below shows the `addBookToCollectionSuccess$` effect displaying a different alert depending on the number of books in the collection state. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { + Actions, + ofType, + createEffect, + concatLatestFrom, +} from '@ngrx/effects'; +import { tap } from 'rxjs/operators'; +import { CollectionApiActions } from '../actions'; +import * as fromBooks from '../reducers'; + +@Injectable() +export class CollectionEffects { + private actions$ = inject(Actions); + private store = inject(Store); + + addBookToCollectionSuccess$ = createEffect( + () => { + return this.actions$.pipe( + ofType(CollectionApiActions.addBookSuccess), + concatLatestFrom((_action) => + this.store.select(fromBooks.getCollectionBookIds) + ), + tap(([_action, bookCollection]) => { + if (bookCollection.length === 1) { + window.alert('Congrats on adding your first book!'); + } else { + window.alert( + 'You have added book number ' + bookCollection.length + ); + } + }) + ); + }, + { dispatch: false } + ); +} +``` + + + + + +For performance reasons, use a flattening operator like `concatLatestFrom` to prevent the selector from firing until the correct action is dispatched. + + + +To learn about testing effects that incorporate state, see the [Effects that use State](guide/effects/testing#effect-that-uses-state) section in the testing guide. + +## Using Other Observable Sources for Effects + +Because effects are merely consumers of observables, they can be used without actions and the `ofType` operator. This is useful for effects that don't need to listen to some specific actions, but rather to some other observable source. + +For example, imagine we want to track click events and send that data to our monitoring server. This can be done by creating an effect that listens to the `document` `click` event and emits the event data to our server. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Observable, fromEvent } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { createEffect } from '@ngrx/effects'; + +import { UserActivityService } from '../services/user-activity.service'; + +@Injectable() +export class UserActivityEffects { + private userActivityService = inject(UserActivityService); + + trackUserActivity$ = createEffect( + () => { + return fromEvent(document, 'click').pipe( + concatMap((event) => + this.userActivityService.trackUserActivity(event) + ) + ); + }, + { dispatch: false } + ); +} +``` + + + + + +An example of the `@ngrx/effects` in module-based applications is available at the [following link](https://v17.ngrx.io/guide/effects). + + + + +# Effects Installation + +## Installing with `ng add` + +You can install the Effects to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/effects@latest +``` + +### Optional `ng add` flags + +| flag | description | value type | default value | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `--path` | Path to the module that you wish to add the import for the `EffectsModule` to. | `string` | +| `--flat` | Indicate if a directory is to be created to hold your effects file. | `boolean` | `true` | +| `--skipTests` | When true, does not create test files. | `boolean` | `false` | +| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `EffectsModule` to. | `string` | +| `--module` | Name of file containing the module that you wish to add the import for the `EffectsModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts` | `string` | `app` | +| `--minimal` | When true, only provide minimal setup for the root effects setup. Only registers `EffectsModule.forRoot()` in the provided `module` with an empty array. | `boolean` | `true` | +| `--group` | Group effects file within `effects` folder. | `boolean` | `false` | + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/effects`. +2. Run `npm install` to install those dependencies. +3. Update your `src/app/app.module.ts` > `imports` array with `EffectsModule.forRoot([AppEffects])`. If you provided flags then the command will attempt to locate and update module found by the flags. +4. If the project is using a `standalone bootstrap`, it adds `provideEffects()` into the application config. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/effects --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/effects +``` + + +# Lifecycle + +### ROOT_EFFECTS_INIT + +After all the root effects have been added, the root effect dispatches a `ROOT_EFFECTS_INIT` action. +You can see this action as a lifecycle hook, which you can use in order to execute some code after all your root effects have been added. + + + +```ts +init$ = createEffect(() => { + return this.actions$.pipe( + ofType(ROOT_EFFECTS_INIT), + map(action => ...) + ); +}); +``` + + + +## Effect Metadata + +### Non-dispatching Effects + +Sometimes you don't want effects to dispatch an action, for example when you only want to log or navigate based on an incoming action. But when an effect does not dispatch another action, the browser will crash because the effect is both 'subscribing' to and 'dispatching' the exact same action, causing an infinite loop. To prevent this, add `{ dispatch: false }` to the `createEffect` function as the second argument. + +Usage: + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, createEffect } from '@ngrx/effects'; +import { tap } from 'rxjs/operators'; + +@Injectable() +export class LogEffects { + private actions$ = inject(Actions); + + logActions$ = createEffect( + () => { + return this.actions$.pipe(tap((action) => console.log(action))); + }, + { dispatch: false } + ); +} +``` + + + +### Resubscribe on Error + +Starting with version 8, when an error happens in the effect's main stream it is +reported using Angular's `ErrorHandler`, and the source effect is +**automatically** resubscribed to (instead of completing), so it continues to +listen to all dispatched Actions. By default, effects are resubscribed up to 10 +errors. + +Generally, errors should be handled by users. However, for the cases where errors were missed, +this new behavior adds an additional safety net. + +In some cases where particular RxJS operators are used, the new behavior might +produce unexpected results. For example, if the `startWith` operator is within the +effect's pipe then it will be triggered again. + +To disable resubscriptions add `{useEffectsErrorHandler: false}` to the `createEffect` +metadata (second argument). + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, ofType, createEffect } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { catchError, exhaustMap, map } from 'rxjs/operators'; +import { LoginPageActions, AuthApiActions } from '../actions'; +import { AuthService } from '../services/auth.service'; + +@Injectable() +export class AuthEffects { + private actions$ = inject(Actions); + private authService = inject(AuthService); + + logins$ = createEffect( + () => { + return this.actions$.pipe( + ofType(LoginPageActions.login), + exhaustMap((action) => + this.authService.login(action.credentials).pipe( + map((user) => AuthApiActions.loginSuccess({ user })), + catchError((error) => + of(AuthApiActions.loginFailure({ error })) + ) + ) + ) + // Errors are handled and it is safe to disable resubscription + ); + }, + { useEffectsErrorHandler: false } + ); +} +``` + + + +### Customizing the Effects Error Handler + +The behavior of the default resubscription handler can be customized +by providing a custom handler using the `EFFECTS_ERROR_HANDLER` injection token. + +This allows you to provide a custom behavior, such as only retrying on +certain "retryable" errors, or change the maximum number of retries (it's set to +10 by default). + + + +```ts +import { ErrorHandler, NgModule } from '@angular/core'; +import { Observable, throwError } from 'rxjs'; +import { retryWhen, mergeMap } from 'rxjs/operators'; +import { Action } from '@ngrx/store'; +import { EffectsModule, EFFECTS_ERROR_HANDLER } from '@ngrx/effects'; +import { MoviesEffects } from './effects/movies.effects'; +import { + CustomErrorHandler, + isRetryable, +} from '../custom-error-handler'; + +export function effectResubscriptionHandler( + observable$: Observable, + errorHandler?: CustomErrorHandler +): Observable { + return observable$.pipe( + retryWhen((errors) => + errors.pipe( + mergeMap((e) => { + if (isRetryable(e)) { + return errorHandler.handleRetryableError(e); + } + + errorHandler.handleError(e); + return throwError(() => e); + }) + ) + ) + ); +} + +bootstrapApplication(AppComponent, { + providers: [ + { + provide: EFFECTS_ERROR_HANDLER, + useValue: effectResubscriptionHandler, + }, + { + provide: ErrorHandler, + useClass: CustomErrorHandler, + }, + ], +}); +``` + + + +## Controlling Effects + +### OnInitEffects + +Implement this interface to dispatch a custom action after the effect has been added. +You can listen to this action in the rest of the application to execute something after the effect is registered. + +Usage: + + + +```ts +class UserEffects implements OnInitEffects { + ngrxOnInitEffects(): Action { + return { type: '[UserEffects]: Init' }; + } +} +``` + + + +### OnRunEffects + +By default, effects are merged and subscribed to the store. Implement the `OnRunEffects` interface to control the lifecycle of the resolved effects. + +Usage: + + + +```ts +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { exhaustMap, takeUntil, tap } from 'rxjs/operators'; +import { + Actions, + OnRunEffects, + EffectNotification, + ofType, + createEffect, +} from '@ngrx/effects'; + +@Injectable() +export class UserEffects implements OnRunEffects { + private actions$ = inject(Actions); + + updateUser$ = createEffect( + () => { + return this.actions$.pipe( + ofType('UPDATE_USER'), + tap((action) => { + console.log(action); + }) + ); + }, + { dispatch: false } + ); + + ngrxOnRunEffects(resolvedEffects$: Observable) { + return this.actions$.pipe( + ofType('LOGGED_IN'), + exhaustMap(() => + resolvedEffects$.pipe( + takeUntil(this.actions$.pipe(ofType('LOGGED_OUT'))) + ) + ) + ); + } +} +``` + + + +### Identify Effects Uniquely + +By default, each Effects class is registered once regardless of how many times the Effect class is loaded. +By implementing this interface, you define a unique identifier to register an Effects class instance multiple times. + +Usage: + + + +```ts +class EffectWithIdentifier implements OnIdentifyEffects { + constructor(private effectIdentifier: string) {} + + ngrxOnIdentifyEffects() { + return this.effectIdentifier; + } +} +``` + + + + +# Effects operators + +As part of the `Effects` library, NgRx provides some useful operators that are frequently +used. + +## `ofType` + +The `ofType` operator filters the stream of actions based on either string +values (that represent `type`s of actions) or Action Creators. + +The generic for the `Actions` must be provided in order for type +inference to work properly with string values. Action Creators that are based on +`createAction` function do not have the same limitation. + +The `ofType` operator takes up to 5 arguments with proper type inference. It can +take even more, however the type would be inferred as an `Action` interface. + + + +```ts +import { Injectable, inject } from '@angular/core'; +import { Actions, ofType, createEffect } from '@ngrx/effects'; +import { of } from 'rxjs'; +import { catchError, exhaustMap, map } from 'rxjs/operators'; +import { LoginPageActions, AuthApiActions } from '../actions'; +import { Credentials } from '../models/user'; +import { AuthService } from '../services/auth.service'; + +@Injectable() +export class AuthEffects { + private actions$ = inject(Actions); + private authService = inject(AuthService); + + login$ = createEffect(() => { + return this.actions$.pipe( + // Filters by Action Creator 'login' + ofType(LoginPageActions.login), + exhaustMap((action) => + this.authService.login(action.credentials).pipe( + map((user) => AuthApiActions.loginSuccess({ user })), + catchError((error) => + of(AuthApiActions.loginFailure({ error })) + ) + ) + ) + ); + }); +} +``` + + + + +# Effects Testing + +## Test helpers + +### `provideMockActions` + +An Effect subscribes to the `Actions` Observable to perform side effects. +`provideMockActions` provides a mock provider of the `Actions` Observable to subscribe to, for each test individually. + + + +```ts +import { provideMockActions } from '@ngrx/effects/testing'; + +let actions$ = new Observable(); + +TestBed.configureTestingModule({ + providers: [provideMockActions(() => actions$)], +}); +``` + + + +Later in the test cases, we assign the `actions$` variable to a stream of actions. + + + +```ts +// by creating an Observable +actions$ = of({ type: 'Action One' }); + +// or by using a marble diagram +actions$ = hot('--a-', { a: { type: 'Action One' } }); +``` + + + +### Effects with parameters + +For time dependant effects, for example `debounceTime`, we must be able override the default RxJS scheduler with the `TestScheduler` during our test. +That's why we create the effect as a function with parameters. By doing this we can assign default parameter values for the effect, and override these values later in the test cases. + +This practice also allows us to hide the implementation details of the effect. +In the `debounceTime` test case, we can set the debounce time to a controlled value. + + + +```ts +search$ = createEffect(() => ({ + // assign default values + debounce = 300, + scheduler = asyncScheduler +} = {}) => + this.actions$.pipe( + ofType(BookActions.search), + debounceTime(debounce, scheduler), + ... + ) +); +``` + + + + + +```ts +// override the default values +effects.search$({ + debounce: 30, + scheduler: getTestScheduler(), +}); +``` + + + +## Testing practices + +### Marble diagrams + +Testing Effects via marble diagrams is particularly useful when the Effect is time sensitive or when the Effect has a lot of behavior. + + + +For a detailed look on the marble syntax, see [Writing marble tests](https://rxjs.dev/guide/testing/marble-testing). + +The `hot`, `cold`, and `toBeObservable` methods are imported from [`jasmine-marbles`](https://www.npmjs.com/package/jasmine-marbles). + + + + + +```ts +// create an actions stream to represent a user that is typing +actions$ = hot('-a-b-', { + a: { type: '[Customers Page] Search Customers', name: 'J' }, + b: { type: '[Customers Page] Search Customers', name: 'Jes' }, +}) + +// mock the service to prevent an HTTP request to return an array of customers +customersServiceSpy.searchCustomers.and.returnValue( + cold('--a|', { a: [...] }) +); + +// expect the first action to debounce and not to dispatch +// expect the second action to result in a SUCCESS action +const expected = hot('-------a', { + a: { + type: '[Customers API] Search Customers Success', + customers: [...], + }, +}); + +expect( + effects.searchCustomers$({ + debounce: 20, + scheduler: getTestScheduler(), + }) +).toBeObservable(expected); +``` + + + +### With `TestScheduler` + +Instead of using `jasmine-marbles`, we can also run tests with the [RxJS `TestScheduler`](https://rxjs.dev/guide/testing/marble-testing). + +To use the `TestScheduler` we first have to instantiate it, +this can be done in the test case or within a `beforeEach` block. + + + +```ts +import { TestScheduler } from 'rxjs/testing'; + +let testScheduler: TestScheduler; + +beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); +}); +``` + + + +The `TestScheduler` provides a `run` method which expects a callback, it's here where we write the test for an effect. +The callback method provides helper methods to mock Observable streams, and also assertion helper methods to verify the output of a stream. + + + +```ts +// more info about the API can be found at https://rxjs.dev/guide/testing/marble-testing#api +testScheduler.run(({ cold, hot, expectObservable }) => { + // use the `hot` and `cold` helper methods to create the action and service streams + actions$ = hot('-a', { a : { type: '[Customers Page] Get Customers' }}); + customersServiceSpy.getAllCustomers.and.returnValue(cold('--a|', { a: [...] })); + + // use the `expectObservable` helper method to assert if the output matches the expected output + expectObservable(effects.getAll$).toBe('---c', { + c: { + type: '[Customers API] Get Customers Success', + customers: [...], + } + }); +}); +``` + + + +By using the `TestScheduler` we can also test effects dependent on a scheduler. +Instead of creating an effect as a method to override properties in test cases, as shown in [`Effects with parameters`](#effects-with-parameters), we can rewrite the test case by using the `TestScheduler`. + + + +```ts +testScheduler.run(({ cold, hot, expectObservable }) => { + // create an actions stream to represent a user that is typing + actions$ = hot('-a-b-', { + a: { type: '[Customers Page] Search Customers', name: 'J' }, + b: { type: '[Customers Page] Search Customers', name: 'Jes' }, + }) + + // mock the service to prevent an HTTP request to return an array of customers + customersServiceSpy.searchCustomers.and.returnValue( + cold('--a|', { a: [...] }) + ); + + // the `300ms` is the set debounce time + // the `5ms` represents the time for the actions stream and the service to return a value + expectObservable(effects.searchCustomers).toBe('300ms 5ms c', { + c: { + type: '[Customers API] Search Customers Success', + customers: [...], + }, + }); +}); +``` + + + +### With Observables + +To test simple Effects, it might be easier to create an Observable instead of using a marble diagram. + + + +```ts +// create an actions stream and immediately dispatch a GET action +actions$ = of({ type: '[Customers Page] Get Customers' }); + +// mock the service to prevent an HTTP request +customersServiceSpy.getAllCustomers.and.returnValue(of([...])); + +// subscribe to the Effect stream and verify it dispatches a SUCCESS action +effects.getAll$.subscribe(action => { + expect(action).toEqual({ + type: '[Customers API] Get Customers Success', + customers: [...], + }); + done(); +}); +``` + + + +### With `ReplaySubject` + +As an alternative, it's also possible to use `ReplaySubject`. + + + +```ts +// create a ReplaySubject +actions$ = new ReplaySubject(1); + +// mock the service to prevent an HTTP request +customersServiceSpy.getAllCustomers.and.returnValue(of([...])); + +// dispatch the GET action +(actions$ as ReplaySubject).next({ type: '[Customers Page] Get Customers' }) + +// subscribe to the Effect stream and verify it dispatches a SUCCESS action +effects.getAll$.subscribe(action => { + expect(action).toEqual({ + type: '[Customers API] Get Customers Success', + customers: [...], + }); + done(); +}); +``` + + + +## Examples + +### A non-dispatching Effect + +Until now, we only saw Effects that dispatch an Action and we verified the dispatched action. +With an Effect that does not dispatch an action, we can't verify the Effects stream. +What we can do, is verify the side-effect has been called. + +An example of this is to verify we navigate to the correct page. + + + +```ts +it('should navigate to the customers detail page', () => { + actions$ = of({ + type: '[Customers Page] Customer Selected', + name: 'Bob', + }); + + // create a spy to verify the navigation will be called + spyOn(router, 'navigateByUrl'); + + // subscribe to execute the Effect + effects.selectCustomer$.subscribe(); + + // verify the navigation has been called + expect(router.navigateByUrl).toHaveBeenCalledWith('customers/bob'); +}); +``` + + + +### Effect that uses state + +Leverage [`MockStore`](/guide/store/testing#using-a-mock-store) and [`MockSelectors`](/guide/store/testing#using-mock-selectors) to test Effects that are selecting slices of the state. + +An example of this is to not fetch an entity (customer in this case) when it's already in the store state. + + + +```ts +let actions$: Observable; + +TestBed.configureTestingModule({ + providers: [ + CustomersEffects, + provideMockActions(() => actions$), + // mock the Store and the selectors that are used within the Effect + provideMockStore({ + selectors: [ + { + selector: selectCustomers, + value: { + Bob: { name: 'Bob' }, + }, + }, + ], + }), + ], +}); + +effects = TestBed.inject(CustomersEffects); + +it('should not fetch if the user is already in the store', () => { + actions$ = hot('-a--', { + a: { type: '[Customers Page] Search Customers', name: 'Bob' }, + }); + + // there is no output, because Bob is already in the Store state + const expected = hot('----'); + + expect(effects.getByName$).toBeObservable(expected); +}); +``` + + + +### Setup without `TestBed` + +Instead of using the Angular `TestBed`, we can instantiate the Effect class. + + + +```ts +it('should get customers', () => { + // instead of using `provideMockActions`, + // define the actions stream by creating a new `Actions` instance + const actions = new Actions( + hot('-a--', { + a: { type: '[Customers Page] Get Customers' }, + }) + ); + + // create the effect + const effects = new CustomersEffects(actions, customersServiceSpy); + + const expected = hot('-a--', { + a: { + type: '[Customers API] Get Customers Success', + customers: [...], + } + }); + + // expect remains the same + expect(effects.getAll$).toBeObservable(expected); +}) +``` + + + +For an Effect with store interaction, use `createMockStore` to create a new instance of `MockStore`. + + + +```ts +it('should get customers', () => { + // create the store, and provide selectors. + const store = createMockStore({ + selectors: [ + { selector: selectCustomers, value: { Bob: { name: 'Bob' } } }, + ], + }); + + // instead of using `provideMockActions`, + // define the actions stream by creating a new `Actions` instance + const actions = new Actions( + hot('-a--', { + a: { + type: '[Search Customers Page] Get Customer', + name: 'Bob', + }, + }) + ); + + // create the effect + const effects = new CustomersEffects( + store as Store, + actions, + customersServiceSpy + ); + + // there is no output, because Bob is already in the Store state + const expected = hot('----'); + + expect(effects.getByName$).toBeObservable(expected); +}); +``` + + + +### Functional Effects + +Functional effects can be tested like any other function. If we inject all dependencies as effect function arguments, `TestBed` is not required to mock dependencies. Instead, we can pass fake instances as input arguments to the functional effect. + + + +```ts +import { of } from 'rxjs'; + +import { loadActors } from './actors.effects'; +import { ActorsService } from './actors.service'; +import { actorsMock } from './actors.mock'; +import { ActorsPageActions } from './actors-page.actions'; +import { ActorsApiActions } from './actors-api.actions'; + +it('loads actors successfully', (done) => { + const actorsServiceMock = { + getAll: () => of(actorsMock), + } as ActorsService; + const actionsMock$ = of(ActorsPageActions.opened()); + + loadActors(actionsMock$, actorsServiceMock).subscribe((action) => { + expect(action).toEqual( + ActorsApiActions.actorsLoadedSuccess({ actors: actorsMock }) + ); + done(); + }); +}); +``` + + + + + +You can check the `loadActors` effect implementation [here](guide/effects#functional-effects). + + + + +# Entity Adapter + +## createEntityAdapter + +A method for returning a generic entity adapter for a single entity state collection. The +returned adapter provides many adapter methods for performing operations +against the collection type. The method takes an object with 2 properties for configuration. + +- `selectId`: A method for selecting the primary id for the collection. Optional when the entity has a primary key of `id` +- `sortComparer`: A compare function used to [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) the collection. The comparer function is only needed if the collection needs to be sorted before being displayed. Set to `false` to leave the collection unsorted, which is more performant during CRUD operations. + +Usage: + + + +```ts +import { + EntityState, + EntityAdapter, + createEntityAdapter, +} from '@ngrx/entity'; + +export interface User { + id: string; + name: string; +} + +export interface State extends EntityState { + // additional entities state properties + selectedUserId: string | null; +} + +export function selectUserId(a: User): string { + //In this case this would be optional since primary key is id + return a.id; +} + +export function sortByName(a: User, b: User): number { + return a.name.localeCompare(b.name); +} + +export const adapter: EntityAdapter = createEntityAdapter( + { + selectId: selectUserId, + sortComparer: sortByName, + } +); +``` + + + +## Adapter Methods + +These methods are provided by the adapter object returned +when using createEntityAdapter. The methods are used inside your reducer function to manage +the entity collection based on your provided actions. + +### getInitialState + +Returns the `initialState` for entity state based on the provided type. Additional state is also provided through the provided configuration object. The initialState is provided to your reducer function. + +Usage: + + + +```ts +import { Action, createReducer } from '@ngrx/store'; +import { + EntityState, + EntityAdapter, + createEntityAdapter, +} from '@ngrx/entity'; + +export interface User { + id: string; + name: string; +} + +export interface State extends EntityState { + // additional entities state properties + selectedUserId: string | null; +} + +export const initialState: State = adapter.getInitialState({ + // additional entity state properties + selectedUserId: null, +}); + +export const userReducer = createReducer(initialState); +``` + + + +## Adapter Collection Methods + +The entity adapter also provides methods for operations against an entity. These methods can change +one to many records at a time. Each method returns the newly modified state if changes were made and the same +state if no changes were made. + +- `addOne`: Add one entity to the collection. +- `addMany`: Add multiple entities to the collection. +- `setAll`: Replace current collection with provided collection. +- `setOne`: Add or Replace one entity in the collection. +- `setMany`: Add or Replace multiple entities in the collection. +- `removeOne`: Remove one entity from the collection. +- `removeMany`: Remove multiple entities from the collection, by id or by predicate. +- `removeAll`: Clear entity collection. +- `updateOne`: Update one entity in the collection. Supports partial updates. +- `updateMany`: Update multiple entities in the collection. Supports partial updates. +- `upsertOne`: Add or Update one entity in the collection. +- `upsertMany`: Add or Update multiple entities in the collection. +- `mapOne`: Update one entity in the collection by defining a map function. +- `map`: Update multiple entities in the collection by defining a map function, similar to [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). + +Usage: + + + +```ts +export interface User { + id: string; + name: string; +} +``` + + + + + +```ts +import { createAction, props } from '@ngrx/store'; +import { + Update, + EntityMap, + EntityMapOne, + Predicate, +} from '@ngrx/entity'; + +import { User } from '../models/user.model'; + +export const loadUsers = createAction( + '[User/API] Load Users', + props<{ users: User[] }>() +); +export const setUsers = createAction( + '[User/API] Set Users', + props<{ users: User[] }>() +); +export const addUser = createAction( + '[User/API] Add User', + props<{ user: User }>() +); +export const setUser = createAction( + '[User/API] Set User', + props<{ user: User }>() +); +export const upsertUser = createAction( + '[User/API] Upsert User', + props<{ user: User }>() +); +export const addUsers = createAction( + '[User/API] Add Users', + props<{ users: User[] }>() +); +export const upsertUsers = createAction( + '[User/API] Upsert Users', + props<{ users: User[] }>() +); +export const updateUser = createAction( + '[User/API] Update User', + props<{ update: Update }>() +); +export const updateUsers = createAction( + '[User/API] Update Users', + props<{ updates: Update[] }>() +); +export const mapUser = createAction( + '[User/API] Map User', + props<{ entityMap: EntityMapOne }>() +); +export const mapUsers = createAction( + '[User/API] Map Users', + props<{ entityMap: EntityMap }>() +); +export const deleteUser = createAction( + '[User/API] Delete User', + props<{ id: string }>() +); +export const deleteUsers = createAction( + '[User/API] Delete Users', + props<{ ids: string[] }>() +); +export const deleteUsersByPredicate = createAction( + '[User/API] Delete Users By Predicate', + props<{ predicate: Predicate }>() +); +export const clearUsers = createAction('[User/API] Clear Users'); +``` + + + + + +```ts +import { Action, createReducer, on } from '@ngrx/store'; +import { + EntityState, + EntityAdapter, + createEntityAdapter, +} from '@ngrx/entity'; +import { User } from '../models/user.model'; +import * as UserActions from '../actions/user.actions'; + +export interface State extends EntityState { + // additional entities state properties + selectedUserId: string | null; +} + +export const adapter: EntityAdapter = + createEntityAdapter(); + +export const initialState: State = adapter.getInitialState({ + // additional entity state properties + selectedUserId: null, +}); + +export const userReducer = createReducer( + initialState, + on(UserActions.addUser, (state, { user }) => { + return adapter.addOne(user, state); + }), + on(UserActions.setUser, (state, { user }) => { + return adapter.setOne(user, state); + }), + on(UserActions.upsertUser, (state, { user }) => { + return adapter.upsertOne(user, state); + }), + on(UserActions.addUsers, (state, { users }) => { + return adapter.addMany(users, state); + }), + on(UserActions.upsertUsers, (state, { users }) => { + return adapter.upsertMany(users, state); + }), + on(UserActions.updateUser, (state, { update }) => { + return adapter.updateOne(update, state); + }), + on(UserActions.updateUsers, (state, { updates }) => { + return adapter.updateMany(updates, state); + }), + on(UserActions.mapUser, (state, { entityMap }) => { + return adapter.mapOne(entityMap, state); + }), + on(UserActions.mapUsers, (state, { entityMap }) => { + return adapter.map(entityMap, state); + }), + on(UserActions.deleteUser, (state, { id }) => { + return adapter.removeOne(id, state); + }), + on(UserActions.deleteUsers, (state, { ids }) => { + return adapter.removeMany(ids, state); + }), + on(UserActions.deleteUsersByPredicate, (state, { predicate }) => { + return adapter.removeMany(predicate, state); + }), + on(UserActions.loadUsers, (state, { users }) => { + return adapter.setAll(users, state); + }), + on(UserActions.setUsers, (state, { users }) => { + return adapter.setMany(users, state); + }), + on(UserActions.clearUsers, (state) => { + return adapter.removeAll({ ...state, selectedUserId: null }); + }) +); + +export const getSelectedUserId = (state: State) => + state.selectedUserId; + +// get the selectors +const { selectIds, selectEntities, selectAll, selectTotal } = + adapter.getSelectors(); + +// select the array of user ids +export const selectUserIds = selectIds; + +// select the dictionary of user entities +export const selectUserEntities = selectEntities; + +// select the array of users +export const selectAllUsers = selectAll; + +// select the total user count +export const selectUserTotal = selectTotal; +``` + + + +### Entity Updates + +There are a few caveats to be aware of when updating entities using the entity adapter. + +The first is that `updateOne` and `updateMany` make use of the `Update` interface shown below. This supports partial updates. + +```typescript +interface UpdateStr { + id: string; + changes: Partial; +} + +interface UpdateNum { + id: number; + changes: Partial; +} + +type Update = UpdateStr | UpdateNum; +``` + +Secondly, `upsertOne` and `upsertMany` will perform an insert or update. These methods do not support partial updates. + +### Entity Selectors + +The `getSelectors` method returned by the created entity adapter provides functions for selecting information from the entity. + +The `getSelectors` method takes a selector function as its only argument to select the piece of state for a defined entity. + +Usage: + + + +```ts +import { + createSelector, + createFeatureSelector, + ActionReducerMap, +} from '@ngrx/store'; +import * as fromUser from './user.reducer'; + +export interface State { + users: fromUser.State; +} + +export const reducers: ActionReducerMap = { + users: fromUser.reducer, +}; + +export const selectUserState = + createFeatureSelector('users'); + +export const selectUserIds = createSelector( + selectUserState, + fromUser.selectUserIds // shorthand for usersState => fromUser.selectUserIds(usersState) +); +export const selectUserEntities = createSelector( + selectUserState, + fromUser.selectUserEntities +); +export const selectAllUsers = createSelector( + selectUserState, + fromUser.selectAllUsers +); +export const selectUserTotal = createSelector( + selectUserState, + fromUser.selectUserTotal +); +export const selectCurrentUserId = createSelector( + selectUserState, + fromUser.getSelectedUserId +); + +export const selectCurrentUser = createSelector( + selectUserEntities, + selectCurrentUserId, + (userEntities, userId) => userId && userEntities[userId] +); +``` + + + + +# @ngrx/entity + +Entity State adapter for managing record collections. + +Entity provides an API to manipulate and query entity collections. + +- Reduces boilerplate for creating reducers that manage a collection of models. +- Provides performant CRUD operations for managing entity collections. +- Extensible type-safe adapters for selecting entity information. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/entity/install) page. + +## Entity and class instances + +Entity promotes the use of plain JavaScript objects when managing collections. _ES6 class instances will be transformed into plain JavaScript objects when entities are managed in a collection_. This provides you with some assurances when managing these entities: + +1. Guarantee that the data structures contained in state don't themselves contain logic, reducing the chance that they'll mutate themselves. +2. State will always be serializable allowing you to store and rehydrate from browser storage mechanisms like local storage. +3. State can be inspected via the Redux Devtools. + +This is one of the [core principles](docs) of NgRx. The [Redux docs](https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state) also offers some more insight into this constraint. + + +# Entity Installation + +## Installing with `ng add` + +You can install the Entity package to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/entity@latest +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/entity`. +2. Run `npm install` to install those dependencies. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/entity --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/entity +``` + + +# Entity Interfaces + +## EntityState + +The Entity State is a predefined generic interface for a given entity collection with the following interface: + + + +```ts +interface EntityState { + ids: string[] | number[]; + entities: { [id: string | id: number]: V }; +} +``` + + + +- `ids`: An array of all the primary ids in the collection +- `entities`: A dictionary of entities in the collection indexed by the primary id + +Extend this interface to provide any additional properties for the entity state. + +Usage: + + + +```ts +export interface User { + id: string; + name: string; +} + +export interface State extends EntityState { + // additional entity state properties + selectedUserId: string | null; +} +``` + + + +## EntityAdapter + +Provides a generic type interface for the provided entity adapter. The entity adapter provides many collection methods for managing the entity state. + +Usage: + + + +```ts +export const adapter: EntityAdapter = + createEntityAdapter(); +``` + + + + +# Additional Entity State Properties Update + +It's possible to add extra properties to a `State` extending from `EntityState`. These properties must be updated manually. Just like in a non-entity state, we can update the added properties in the reducer. This can be done with or without using the `@ngrx/entity` helper functions. + +The steps below show you how to extend the [Entity Adapter](guide/entity/adapter) example. + +Usage: + +Declare the `selectedUserId` as an additional property in the interface. + + + +```ts +import { + EntityState, + EntityAdapter, + createEntityAdapter, +} from '@ngrx/entity'; + +export interface User { + id: string; + name: string; +} + +export interface State extends EntityState { + // additional state property + selectedUserId: string | null; +} + +export const adapter: EntityAdapter = + createEntityAdapter(); +``` + + + +Then create an action to update the `selectedUserId` + + + +```ts +import { createAction, props } from '@ngrx/store'; +import { Update } from '@ngrx/entity'; + +import { User } from '../models/user.model'; + +export const selectUser = createAction( + '[Users Page] Select User', + props<{ userId: string }>() +); +export const loadUsers = createAction( + '[User/API] Load Users', + props<{ users: User[] }>() +); +``` + + + +The entity adapter is only used to update the `EntityState` properties. The additional state properties should be updated same as normal state properties, as the example below. + + + +```ts +import { + EntityState, + EntityAdapter, + createEntityAdapter, +} from '@ngrx/entity'; +import { Action, createReducer, on } from '@ngrx/store'; +import { User } from '../models/user.model'; +import * as UserActions from '../actions/user.actions'; + +export interface State extends EntityState { + // additional state property + selectedUserId: string | null; +} + +export const adapter: EntityAdapter = + createEntityAdapter(); + +export const initialState: State = adapter.getInitialState({ + // additional entity state properties + selectedUserId: null, +}); + +export const reducer = createReducer( + initialState, + on(UserActions.selectUser, (state, { userId }) => { + return { ...state, selectedUserId: userId }; + }), + on(UserActions.loadUsers, (state, { users }) => { + return adapter.addMany(users, { ...state, selectedUserId: null }); + }) +); +``` + + + + +# Using Entity Adapter with Feature Creator + +This recipe demonstrates how to combine [entity adapter](/guide/entity/adapter#entity-adapter) (selectors) within [extraSelectors](/guide/store/feature-creators#providing-extra-selectors). + +As an example, let's look at some code from a User state management feature. + +Start by defining the state for the `User` feature. + + + +```ts +import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { User } from './user.model'; + +export interface State extends EntityState { + selectedUserId: string | null; +} + +const adapter = createEntityAdapter(); + +export const initialState: State = adapter.getInitialState({ + selectedUserId: null, +}); +``` + + + +Then, we define the `User` actions: `addUser` and `selectUser`. + + + +```ts +import { createActionGroup, props } from '@ngrx/store'; +import { User } from './user.model'; + +export const UserListPageActions = createActionGroup({ + source: 'User List Page', + events: { + addUser: props<{ user: User }>(), + selectUser: props<{ userId: string }>(), + }, +}); +``` + + + +Then use the `createReducer` function to create a reducer for the `User` feature. + + + +```ts +import { createReducer, on } from '@ngrx/store'; +import { UserListPageActions } from './user-list-page.actions'; + +const reducer = createReducer( + initialState, + on(UserListPageActions.addUser, (state, { user }) => + adapter.addOne(user, state) + ), + on(UserListPageActions.selectUser, (state, { userId }) => ({ + ...state, + selectedUserId: userId, + })) +); +``` + + + +Then create the `User` feature using the `createFeature` function. +This generates all the basic selectors automatically, and we can specify extra selectors using the `extraSelectors` option. + + + +```ts +import { createFeature, createSelector } from '@ngrx/store'; + +export const usersFeature = createFeature({ + name: 'users', + reducer, + extraSelectors: ({ + selectUsersState, + selectEntities, + selectSelectedUserId, + }) => ({ + ...adapter.getSelectors(selectUsersState), + selectIsUserSelected: createSelector( + selectSelectedUserId, + (selectedId) => selectedId !== null + ), + selectSelectedUser: createSelector( + selectSelectedUserId, + selectEntities, + (selectedId, entities) => + selectedId ? entities[selectedId] : null + ), + }), +}); +``` + + + +To use the selector within a component, `inject` the `Store` and select the data from the state using the selectors generated by the `createFeature` function. + + + +```ts +import { usersFeature } from './users.state'; + +@Component({ + /* ... */ +}) +export class UserListComponent { + private readonly store = inject(Store); + + readonly users$ = this.store.select(usersFeature.selectAll); + readonly isUserSelected$ = this.store.select( + usersFeature.selectIsUserSelected + ); + readonly selectedUser$ = this.store.select( + usersFeature.selectSelectedUser + ); +} +``` + + + + +# ESLint-Plugin Overview + +You can use [ESLint](https://eslint.org/) to follow best practices and avoid common pitfalls in your application. + +The NgRx ESLint Plugin is no different and promotes the key concepts to create maintainable projects. +It consists of [rules](#rules) that are grouped into predefined [configurations](#configurations) for each NgRx package to help you get started quickly. + +By default, all rules have set the severity level to `error`. +Some rules also include a recommendation or an automatic fix using `ng lint --fix`. + +## Configuration and Usage + +### ESLint v8 + +To use the NgRx ESLint Plugin with ESLint v8, add it to your ESLint file (e.g. `.eslintrc.json`). +Add the `@ngrx` plugin to the `plugins` section and add the rules you want to use within your project to the `rules` section. + +```json +{ + "plugins": ["@ngrx"], + "rules": { + "@ngrx/good-action-hygiene": "error" + } +} +``` + +For rules that require type information, the ESLint configuration needs to provide the `parserOptions.project` property, otherwise the rule throws an error. + +```json +{ + "plugins": ["@ngrx"], + "parserOptions": { + "project": "tsconfig.json" + }, + "rules": { + "@ngrx/avoid-cyclic-effects": "error" + } +} +``` + +Instead of adding rules individually, you can use one of the [preconfigured configurations](#configurations) by adding it to the `extends` section. +This automatically includes all rules of the configuration. +To override a specific rule, add it to the `rules` section and adjust the severity level or the configuration. + +```json +{ + "extends": ["plugin:@ngrx/all"], + "rules": { + "@ngrx/good-action-hygiene": "warn" + } +} +``` + +Instead of including all NgRx rules, you can also use a specific configuration for a package. +This is useful if you only use a specific package, as it only includes the rules relevant to that package. + +```json +{ + "extends": ["@ngrx/signals"] +} +``` + +### ESLint v9 + +To use the NgRx ESLint Plugin with ESLint v9, include the desired configurations within your ESLint configuration file (e.g. `eslint.config.js`). +Optionally override some rules via the `rules` property. + +Import the NgRx Plugin via `@ngrx/eslint-plugin/v9` and use one or more predefined [configurations](#configurations) by adding them to the `extends` array. + +```ts +const tseslint = require('typescript-eslint'); +const ngrx = require('@ngrx/eslint-plugin/v9'); + +module.exports = tseslint.config({ + files: ['**/*.ts'], + extends: [ + // πŸ‘‡ Use all rules at once + ...ngrx.configs.all, + // πŸ‘‡ Or only import the rules for a specific package + ...ngrx.configs.store, + ...ngrx.configs.effects, + ...ngrx.configs.componentStore, + ...ngrx.configs.operators, + ...ngrx.configs.signals, + ], + rules: { + // πŸ‘‡ Configure specific rules + '@ngrx/with-state-no-arrays-at-root-level': 'warn', + }, +}); +``` + +## Rules + + + + +### component-store + +| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information | +| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- | +| [@ngrx/avoid-combining-component-store-selectors](/guide/eslint-plugin/rules/avoid-combining-component-store-selectors) | Prefer combining selectors at the selector level. | suggestion | No | No | No | No | +| [@ngrx/avoid-mapping-component-store-selectors](/guide/eslint-plugin/rules/avoid-mapping-component-store-selectors) | Avoid mapping logic outside the selector level. | problem | No | No | No | No | +| [@ngrx/require-super-ondestroy](/guide/eslint-plugin/rules/require-super-ondestroy) | Overriden ngOnDestroy method in component stores require a call to super.ngOnDestroy(). | problem | No | No | No | No | +| [@ngrx/updater-explicit-return-type](/guide/eslint-plugin/rules/updater-explicit-return-type) | `Updater` should have an explicit return type. | problem | No | No | No | No | + +### effects + +| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information | +| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- | +| [@ngrx/avoid-cyclic-effects](/guide/eslint-plugin/rules/avoid-cyclic-effects) | Avoid `Effect` that re-emit filtered actions. | problem | No | No | No | Yes | +| [@ngrx/no-dispatch-in-effects](/guide/eslint-plugin/rules/no-dispatch-in-effects) | `Effect` should not call `store.dispatch`. | suggestion | No | Yes | No | No | +| [@ngrx/no-effects-in-providers](/guide/eslint-plugin/rules/no-effects-in-providers) | `Effect` should not be listed as a provider if it is added to the `EffectsModule`. | problem | Yes | No | No | No | +| [@ngrx/no-multiple-actions-in-effects](/guide/eslint-plugin/rules/no-multiple-actions-in-effects) | `Effect` should not return multiple actions. | problem | No | No | No | Yes | +| [@ngrx/prefer-action-creator-in-of-type](/guide/eslint-plugin/rules/prefer-action-creator-in-of-type) | Using `action creator` in `ofType` is preferred over `string`. | suggestion | No | No | No | No | +| [@ngrx/prefer-effect-callback-in-block-statement](/guide/eslint-plugin/rules/prefer-effect-callback-in-block-statement) | A block statement is easier to troubleshoot. | suggestion | Yes | No | No | No | +| [@ngrx/use-effects-lifecycle-interface](/guide/eslint-plugin/rules/use-effects-lifecycle-interface) | Ensures classes implement lifecycle interfaces corresponding to the declared lifecycle methods. | suggestion | Yes | No | No | No | + +### operators + +| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information | +| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | --------------- | ------------ | ------------------------- | +| [@ngrx/prefer-concat-latest-from](/guide/eslint-plugin/rules/prefer-concat-latest-from) | Use `concatLatestFrom` instead of `withLatestFrom` to prevent the selector from firing until the correct `Action` is dispatched. | problem | Yes | No | Yes | No | + +### signals + +| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information | +| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- | +| [@ngrx/prefer-protected-state](/guide/eslint-plugin/rules/prefer-protected-state) | A Signal Store prefers protected state | suggestion | No | Yes | No | No | +| [@ngrx/signal-state-no-arrays-at-root-level](/guide/eslint-plugin/rules/signal-state-no-arrays-at-root-level) | signalState should accept a record or dictionary as an input argument. | problem | No | No | No | No | +| [@ngrx/signal-store-feature-should-use-generic-type](/guide/eslint-plugin/rules/signal-store-feature-should-use-generic-type) | A custom Signal Store feature that accepts an input should define a generic type. | problem | Yes | No | No | No | +| [@ngrx/with-state-no-arrays-at-root-level](/guide/eslint-plugin/rules/with-state-no-arrays-at-root-level) | withState should accept a record or dictionary as an input argument. | problem | No | No | No | Yes | + +### store + +| Name | Description | Category | Fixable | Has suggestions | Configurable | Requires type information | +| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------- | ------- | --------------- | ------------ | ------------------------- | +| [@ngrx/avoid-combining-selectors](/guide/eslint-plugin/rules/avoid-combining-selectors) | Prefer combining selectors at the selector level. | suggestion | No | No | No | No | +| [@ngrx/avoid-dispatching-multiple-actions-sequentially](/guide/eslint-plugin/rules/avoid-dispatching-multiple-actions-sequentially) | It is recommended to only dispatch one `Action` at a time. | suggestion | No | No | No | No | +| [@ngrx/avoid-duplicate-actions-in-reducer](/guide/eslint-plugin/rules/avoid-duplicate-actions-in-reducer) | A `Reducer` should handle an `Action` once. | suggestion | No | Yes | No | No | +| [@ngrx/avoid-mapping-selectors](/guide/eslint-plugin/rules/avoid-mapping-selectors) | Avoid mapping logic outside the selector level. | suggestion | No | No | No | No | +| [@ngrx/good-action-hygiene](/guide/eslint-plugin/rules/good-action-hygiene) | Ensures the use of good action hygiene. | suggestion | No | No | No | No | +| [@ngrx/no-multiple-global-stores](/guide/eslint-plugin/rules/no-multiple-global-stores) | There should only be one global store injected. | suggestion | No | Yes | No | No | +| [@ngrx/no-reducer-in-key-names](/guide/eslint-plugin/rules/no-reducer-in-key-names) | Avoid the word "reducer" in the key names. | suggestion | No | Yes | No | No | +| [@ngrx/no-store-subscription](/guide/eslint-plugin/rules/no-store-subscription) | Using the `async` pipe is preferred over `store` subscription. | suggestion | No | No | No | No | +| [@ngrx/no-typed-global-store](/guide/eslint-plugin/rules/no-typed-global-store) | The global store should not be typed. | suggestion | No | Yes | No | No | +| [@ngrx/on-function-explicit-return-type](/guide/eslint-plugin/rules/on-function-explicit-return-type) | `On` function should have an explicit return type. | suggestion | No | Yes | No | No | +| [@ngrx/prefer-action-creator-in-dispatch](/guide/eslint-plugin/rules/prefer-action-creator-in-dispatch) | Using `action creator` in `dispatch` is preferred over `object` or old `Action`. | suggestion | No | No | No | No | +| [@ngrx/prefer-action-creator](/guide/eslint-plugin/rules/prefer-action-creator) | Using `action creator` is preferred over `Action class`. | suggestion | No | No | No | No | +| [@ngrx/prefer-inline-action-props](/guide/eslint-plugin/rules/prefer-inline-action-props) | Prefer using inline types instead of interfaces, types or classes. | suggestion | No | Yes | No | No | +| [@ngrx/prefer-one-generic-in-create-for-feature-selector](/guide/eslint-plugin/rules/prefer-one-generic-in-create-for-feature-selector) | Prefer using a single generic to define the feature state. | suggestion | No | Yes | No | No | +| [@ngrx/prefer-selector-in-select](/guide/eslint-plugin/rules/prefer-selector-in-select) | Using a selector in the `select` is preferred over `string` or `props drilling`. | suggestion | No | No | No | No | +| [@ngrx/prefix-selectors-with-select](/guide/eslint-plugin/rules/prefix-selectors-with-select) | The selector should start with "select", for example "selectEntity". | suggestion | No | Yes | No | No | +| [@ngrx/select-style](/guide/eslint-plugin/rules/select-style) | Selector can be used either with `select` as a pipeable operator or as a method. | suggestion | Yes | No | Yes | No | +| [@ngrx/use-consistent-global-store-name](/guide/eslint-plugin/rules/use-consistent-global-store-name) | Use a consistent name for the global store. | suggestion | No | Yes | Yes | No | + + + +## Configurations + + + + +| Name | +| -------------------------------------------------------------------------------------------------------------------- | +| [all](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/all.json) | +| [component-store](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/component-store.json) | +| [effects](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/effects.json) | +| [operators](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/operators.json) | +| [signals](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/signals.json) | +| [store](https://github.com/ngrx/platform/blob/main/modules/eslint-plugin/src/configs/store.json) | + + + + +# ESLint-Plugin Installation + +## Installing with `ng add` + +You can install the ESLint plugin to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/eslint-plugin +``` + +The command will prompt you to select which config you would like to have preconfigured; you can read the detailed list of available configs [here](guide/eslint-plugin/#configurations). + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/eslint-plugin --save-dev +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/eslint-plugin -D +``` + + +# avoid-combining-component-store-selectors + +Prefer combining selectors at the selector level. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +### Enrich state with other state in component + +```ts +export class Component extends ComponentStore { + all$ = combineLatest( + this.select((state) => state.movies), + this.select((state) => state.books) + ); + + constructor() { + super({ movies: [], books: [] }); + } +} +``` + +### Filter state in component + +```ts +export class Component extends ComponentStore { + movie$ = combineLatest( + this.select((state) => state.movies), + this.select((state) => state.selectedId) + ).pipe(map(([movies, selectedId]) => movies[selectedId])); + + constructor() { + super({ movies: [] }); + } +} +``` + +Examples of **correct** code for this rule: + +### Enrich state with other state in selector + +```ts +export class Component extends ComponentStore { + movies$ = this.select((state) => state.movies); + books$ = this.select((state) => state.books); + all$ = this.select(this.movies$, this.books$, ([movies, books]) => { + return { + movies, + books, + }; + }); + + constructor() { + super({ movies: [], books: [] }); + } +} +``` + +### Filter state in selector + +```ts +export class Component extends ComponentStore { + movies$ = this.select((state) => state.movies); + selectedId$ = this.select((state) => state.selectedId); + movie$ = this.select( + this.movies$, + this.selectedId$, + ([movies, selectedId]) => movies[selectedId] + ); + + constructor() { + super({ movies: [] }); + } +} +``` + + +# avoid-combining-selectors + +Prefer combining selectors at the selector level. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +A selector is a pure function that is used to derive state. +Because a selector is a pure function (and a synchronous function), it's easier to test. + +That's why it's recommended to build a view model by composing multiple selectors into one selector, instead of consuming multiple selector observable streams to create a view model in the component. + +Examples of **incorrect** code for this rule: + +### Enrich state with other state in component + +```ts +export class Component { + vm$ = combineLatest( + this.store.select(selectCustomers), + this.store.select(selectOrders) + ).pipe( + map(([customers, orders]) => { + return customers.map((c) => { + return { + customerId: c.id, + name: c.name, + orders: orders.filter((o) => o.customerId === c.id), + }; + }); + }) + ); +} +``` + +### Filter state in component + +```ts +export class Component { + customer$ = this.store + .select(selectCustomers) + .pipe(withLatestFrom(this.store.select(selectActiveCustomerId))) + .pipe( + map(([customers, customerId]) => { + return customers[customerId]; + }) + ); +} +``` + +Examples of **correct** code for this rule: + +### Enrich state with other state in selector + +```ts +export selectCustomersAndOrders = createSelector( + selectCustomers, + selectOrders, + (customers, orders) => { + return { + customerId: c.id, + name: c.name, + orders: orders.filter((o) => o.customerId === c.id), + } + } +) + +export class Component { + vm$ = this.store.select(selectCustomersAndOrders); +} +``` + +### Filter state in selector + +```ts +export selectActiveCustomer = createSelector( + selectCustomers, + selectActiveCustomerId, + ([customers, customerId]) => { + return customers[customerId]; + } +) + +export class Component { + customer$ = this.store.select(selectActiveCustomer); +} +``` + +## Further reading + +- [Maximizing and Simplifying Component Views with NgRx Selectors - by Brandon Roberts](https://brandonroberts.dev/blog/posts/2020-12-14-maximizing-simplifying-component-views-ngrx-selectors/#building-view-models) + + +# avoid-cyclic-effects + +Avoid `Effect` that re-emit filtered actions. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: Yes +- **Configurable**: No + + + + +## Rule Details + +This rule prevents that the same action as the filtered action is dispatched in an effect, causing an infinite loop. +Effects that are configured with `dispatch: false`, are discarded. + +For the rare cases where you need to re-dispatch the same action, you can disable this rule. + +Examples of **incorrect** code for this rule: + +```ts +class Effect { + details$ = createEffect(() => + this.actions$.pipe( + ofType(fromCustomers.pageLoaded), + map(() => fromCustomers.pageLoaded()) + ) + ); + + constructor(private actions$: Actions) {} +} + +class Effect { + details$ = createEffect(() => + this.actions$.pipe( + ofType(fromCustomers.pageLoaded), + tap(() => alert('Customers loaded')) + ) + ); + + constructor(private actions$: Actions) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +class Effect { + details$ = createEffect(() => + this.actions$.pipe( + ofType(fromCustomers.pageLoaded), + map(() => fromCustomers.pageLoadedSuccess()) + ) + ); + + constructor(private actions$: Actions) {} +} + +class Effect { + details$ = createEffect( + () => + this.actions$.pipe( + ofType(fromCustomers.pageLoaded), + tap(() => alert('Customers loaded')) + ), + { dispatch: false } + ); + + constructor(private actions$: Actions) {} +} +``` + + +# avoid-dispatching-multiple-actions-sequentially + +It is recommended to only dispatch one `Action` at a time. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +An action should be an event that abstracts away the details of store internals. +An action can be a composition of several events, in which case the developer might be tempted to dispatch several actions in sequence. But a better approach is to dispatch one "combining" action, which exactly describes what that event entails. + +Examples of **incorrect** code for this rule: + +```ts +export class Component implement OnInit { + constructor( + private readonly store: Store, + ) {} + + ngOnInit() { + // ⚠ multiple actions dispatched + this.store.dispatch(loadEmployeeList()); + this.store.dispatch(loadCompanyList()); + this.store.dispatch(cleanData()); + } +} +``` + +Examples of **correct** code for this rule: + +```ts +// in component code: +export class Component implement OnInit { + constructor( + private readonly store: Store, + ) {} + + ngOnInit() { + this.store.dispatch(componentLoaded()); + } +} + +// in effect: +export class Effects { + + loadEmployeeList$ = createEffect(() => this.actions.pipe( + ofType(componentLoaded), + exhaustMap(() => this.dataService.loadEmployeeList().pipe( + map(response => loadEmployeeListSuccess(response)), + catchError(error => loadEmployeeListError(error)), + )), + )); + + loadCompanyList$ = createEffect(() => this.actions.pipe( + ofType(componentLoaded), + // handle loadCompanyList + )); + + cleanData$ = createEffect(() => this.actions.pipe( + ofType(componentLoaded), + // handle cleanData + )); + + constructor( + private readonly actions$: Actions, + ) {} +} +``` + + +# avoid-duplicate-actions-in-reducer + +A `Reducer` should handle an `Action` once. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +While it's technically allowed to handle an action more than once in a reducer, this often means something is wrong. Probably this is just a typo or a copy-paste mistake. When that's not the case, it's often desired to rethink and refactor the reducer. + +A valid reason why an action can be handled more than once in a single reducer, is when the reducer is consumed by a [higher-order reducer](https://github.com/ngrx/platform/issues/1956#issuecomment-526720340). If that's the case, this rule isn't triggered. + +Examples of **incorrect** code for this rule: + +```ts +export const reducer = createReducer( + initialState, + on(customerLoaded, (state) => ({ ...state, status: 'loaded' })), + on(customerLoaded, (state) => ({ ...state, status: 'loaded' })) +); +``` + +Examples of **correct** code for this rule: + +```ts +export const reducer = createReducer( + initialState, + on(customerLoaded, (state) => ({ ...state, status: 'loaded' })) +); +``` + + +# avoid-mapping-component-store-selectors + +Avoid mapping logic outside the selector level. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +export class UserStore extends ComponentStore { + loggedInUser$ = this.select((state) => state.loggedInUser); + // ⚠ Avoid mapping logic outside the selector level. + name$ = this.select((state) => state.loggedInUser).pipe( + map((user) => user.name) + ); +} +``` + +Examples of **correct** code for this rule: + +```ts +export class UserStore extends ComponentStore { + loggedInUser$ = this.select((state) => state.loggedInUser); + + name$ = this.select(this.loggedInUser$, (user) => user.name); +} +``` + + +# avoid-mapping-selectors + +Avoid mapping logic outside the selector level. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +A selector is a pure function that is used to derive state. +Because a selector is a pure function (and it's synchronous), it's easier to test. + +That's why it's recommended to put (mapping) logic into a selector, instead of in the component by using the RxJS `map` operator. + +Examples of **incorrect** code for this rule: + +```ts +export class Component { + name$ = this.store + .select(selectLoggedInUser) + .pipe(map((user) => ({ name: user.name }))); +} +``` + +Examples of **correct** code for this rule: + +```ts +// in selectors.ts: +export selectLoggedInUserName = createSelector( + selectLoggedInUser, + (user) => user.name +) + +// in component: +export class Component { + name$ = this.store.select(selectLoggedInUserName) +} +``` + + +# enforce-type-call + +The `type` function must be called. + +- **Type**: problem +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + + +# good-action-hygiene + +Ensures the use of good action hygiene. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +An action should be a unique event in the application. +An action should: + +- tell where it's dispatched +- tell what event has occurred + +The template we use for an action's type is `[Source] Event`. + +Examples of **incorrect** code for this rule: + +```ts +export const customersRefresh = createAction('Refresh Customers'); +export const customersLoadedSuccess = createAction( + 'Customers Loaded Success' +); +``` + +Examples of **correct** code for this rule: + +```ts +export const customersRefresh = createAction( + '[Customers Page] Refresh clicked' +); +export const customersLoadedSuccess = createAction( + '[Customers API] Customers Loaded Success' +); +``` + +## Further reading + +- [Good Action Hygiene with NgRx Mike Ryan](https://www.youtube.com/watch?v=JmnsEvoy-gY) + + +# no-dispatch-in-effects + +`Effect` should not call `store.dispatch`. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +An effect should handle actions and map them to a singular action. +Each effect should be clear and concise and must be understandable to what it does affect. +Dispatching an action from inside an effect instead of (or together with) mapping it to an action can result in painfully hard-to-find bugs and is generally considered a bad practice. + +Examples of **incorrect** code for this rule: + +```ts +export class Effects { + loadData$ = createEffect(() => + this.actions$.pipe( + ofType(loadData), + exhaustMap(() => + this.dataService.getData().pipe( + tap((response) => { + // ⚠ dispatching another action from an effect + if (response.condition) { + this.store.dispatch(anotherAction()); + } + }), + map((response) => loadDataSuccess(response)), + catchError((error) => of(loadDataError(error))) + ) + ) + ) + ); + + constructor( + private readonly actions$: Actions, + private readonly store: Store + ) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +export class Effects { + loadData$ = createEffect(() => + this.actions$.pipe( + ofType(loadData), + exhaustMap(() => + this.dataService.getData().pipe( + map((response) => loadDataSuccess(response)), + catchError((error) => of(loadDataError(error))) + ) + ) + ) + ); + + handleCondition$ = createEffect(() => + this.actions$.pipe( + ofType(loadDataSuccess), + filter((response) => response.condition), + exhaustMap(() => + this.dataService.getOtherData().pipe( + map((data) => anotherAction(data)), + catchError((error) => of(handleConditionError(error))) + ) + ) + ) + ); + + constructor(private readonly actions$: Actions) {} +} +``` + + +# no-effects-in-providers + +`Effect` should not be listed as a provider if it is added to the `EffectsModule`. + +- **Type**: problem +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +An effect class should only be added to the `EffectsModule` by using the `forRoot()` and `forFeature()` methods, **not** by adding the effect class to the Angular providers. + +Examples of **incorrect** code for this rule: + +With `forRoot`: + +```ts +@NgModule({ + imports: [EffectsModule.forRoot([CustomersEffect])], + providers: [CustomersEffect], +}) +export class AppModule {} +``` + +With `forFeature`: + +```ts +@NgModule({ + imports: [EffectsModule.forFeature([CustomersEffect])], + providers: [CustomersEffect], +}) +export class CustomersModule {} +``` + +Examples of **correct** code for this rule: + +With `forRoot`: + +```ts +@NgModule({ + imports: [EffectsModule.forRoot([CustomersEffect])], +}) +export class AppModule {} +``` + +With `forFeature`: + +```ts +@NgModule({ + imports: [EffectsModule.forFeature([CustomersEffect])], +}) +export class CustomersModule {} +``` + +## Further reading + +- [EffectsModule API](api/effects/EffectsModule) + + +# no-multiple-actions-in-effects + +`Effect` should not return multiple actions. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: Yes +- **Configurable**: No + + + + +## Rule Details + +An Effect should map one event (action) to a single other action. +An action can result in several other Effects being triggered, or multiple changes in the reducer, in which case the developer might be tempted to map an Effect to several actions. A more understandable approach is to dispatch one "combining" action that describes what happened (a unique event), rather than multiple actions. + +Examples of **incorrect** code for this rule: + +```ts +export class Effects { + loadEmployeeList$ = createEffect(() => { + return this.actions$.pipe( + ofType(componentLoaded), + exhaustMap(() => + this.dataService.loadEmployeeList().pipe( + switchMap((response) => [ + loadEmployeeListSuccess(response), + loadCompanyList(), + cleanData(), + ]), + catchError((error) => loadEmployeeListError(error)) + ) + ) + ); + }); + + loadCompanyList$ = createEffect(() => { + return this.actions$.pipe( + ofType(loadCompanyList) + // handle loadCompanyList + ); + }); + + cleanData$ = createEffect(() => { + return this.actions$.pipe( + ofType(cleanData) + // handle cleanData + ); + }); + + constructor(private readonly actions$: Actions) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +// in effect: +export class Effects { + loadEmployeeList$ = createEffect(() => { + return this.actions$.pipe( + ofType(componentLoaded), + exhaustMap(() => + this.dataService.loadEmployeeList().pipe( + map((response) => loadEmployeeListSuccess(response)), + catchError((error) => loadEmployeeListError(error)) + ) + ) + ); + }); + + // use the one dispatched action + + loadCompanyList$ = createEffect(() => { + return this.actions$.pipe( + ofType(loadEmployeeListSuccess) + // handle loadCompanyList + ); + }); + + //use the one dispatched action + + cleanData$ = createEffect(() => { + return this.actions$.pipe( + ofType(loadEmployeeListSuccess) + // handle cleanData + ); + }); + + constructor(private readonly actions$: Actions) {} +} +``` + + +# no-multiple-global-stores + +There should only be one global store injected. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +There is only one global store, thus there should also only be one global store injected in a class (component, service, ...). Violating this rule is often paired with violating the [`no-typed-global-store`](guide/eslint-plugin/rules/no-typed-global-store) rule. + +Examples of **incorrect** code for this rule: + +```ts +export class Component { + constructor( + private readonly customersStore: Store, + private readonly catalogStore: Store + ) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +export class Component { + constructor(private readonly store: Store) {} +} +``` + + +# no-reducer-in-key-names + +Avoid the word "reducer" in the key names. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +StoreModule.forRoot({ + customersReducer: customersReducer, +}); + +StoreModule.forFeature({ + customersReducer, +}); + +export const reducers: ActionReducerMap = { + customersReducer: fromCustomers.reducer, +}; +``` + +Examples of **correct** code for this rule: + +```ts +StoreModule.forRoot({ + customers: customersReducer, +}); + +StoreModule.forFeature({ + customers: customersReducer, +}); + +export const reducers: ActionReducerMap = { + customers: fromCustomers.reducer, +}; +``` + +## Further reading + +- [Redux Style Guide: Name State Slices Based On the Stored Data](https://redux.js.org/style-guide/style-guide#name-state-slices-based-on-the-stored-data) + + +# no-store-subscription + +Using the `async` pipe is preferred over `store` subscription. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +ngOnInit() { + this.store.select(selectedItems).subscribe(items => { + this.items = items; + }) +} +``` + +Examples of **correct** code for this rule: + + +```ts +// in code +selectedItems$ = this.store.select(selectedItems) + +// in template +{{ selectedItems$ | async }} +``` + + +# no-typed-global-store + +The global store should not be typed. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Typing the global `Store` is redundant because selectors are type-safe, so adding the generic state interface while injecting the store is unnecessary. +Providing the wrong type can also result in unexpected type-related problems. See [discussion](https://github.com/ngrx/platform/issues/2780) for more info. + +To prevent a misconception that there are multiple stores (and even that multiple stores are injected into the same component, see [`no-multiple-global-stores`](guide/eslint-plugin/rules/no-multiple-global-stores)), we only want to inject 1 global store into components, effects, and services. + +Examples of **incorrect** code for this rule: + +```ts +export class Component { + data$ = this.store.select(data); + + constructor(private readonly store: Store<{ data: Data }>) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +export class Component { + data$ = this.store.select(data); + + constructor(private readonly store: Store) {} +} +``` + + +# on-function-explicit-return-type + +`On` function should have an explicit return type. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +When we use the `on` function to create reducers, we usually copy the state into a new object, and then add the properties that are being modified after that certain action. This may result in unexpected typing problems, we can add new properties into the state that did not exist previously. TypeScript doesn't see this as a problem and might change the state's interface. The solution is to provide an explicit return type to the `on` function callback. + +Examples of **incorrect** code for this rule: + +```ts +export interface AppState { + username: string; +} + +const reducer = createReducer( + { username: '' }, + on(setUsername, (state, action) => ({ + ...state, + username: action.payload, + newProperty: 1, // we added a property that does not exist on `AppState`, and TS won't catch this problem + })) +); +``` + +Examples of **correct** code for this rule: + +```ts +export interface AppState { + username: string; +} + +const reducer = createReducer( + { username: '' }, + on( + setUsername, + (state, action): AppState => ({ + ...state, + username: action.payload, + // adding new properties that do not exist on `AppState` is impossible, as the function return type is explicitly stated + }) + ) +); +``` + + +# prefer-action-creator-in-dispatch + +Using `action creator` in `dispatch` is preferred over `object` or old `Action`. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +store$.dispatch(new CustomAction()); + +this.store$.dispatch(new AuthActions.Login({ type })); + +this.store$.dispatch({ type: 'custom' }); +``` + +Examples of **correct** code for this rule: + +```ts +store$.dispatch(action); + +this.store$.dispatch(BookActions.load()); + +this.store$.dispatch(AuthActions.Login({ payload })); +``` + + +# prefer-action-creator-in-of-type + +Using `action creator` in `ofType` is preferred over `string`. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +effectNOK = createEffect(() => this.actions$.pipe(ofType('PING'))); + +effectNOK1 = createEffect(() => + this.actions$.pipe(ofType(BookActions.load, 'PONG')) +); +``` + +Examples of **correct** code for this rule: + +```ts +effectOK = createEffect(() => + this.actions$.pipe(ofType(userActions.ping.type)) +); +``` + + +# prefer-action-creator + +Using `action creator` is preferred over `Action class`. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +class Test implements Action { + type = '[Customer Page] Load Customer'; +} + +class Test implements ngrx.Action { + readonly type = ActionTypes.success; + + constructor(readonly payload: Payload) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +export const loadUser = createAction('[User Page] Load User'); + +class Test { + type = '[Customer Page] Load Customer'; +} + +class Test implements Action { + member = '[Customer Page] Load Customer'; +} +``` + + +# prefer-concat-latest-from + +Use `concatLatestFrom` instead of `withLatestFrom` to prevent the selector from firing until the correct `Action` is dispatched. + +- **Type**: problem +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: Yes + + + + +## Rule Details + +Using `concatLatestFrom` (a lazy version of `withLatestFrom`) ensures that the selector is only invoked when the effect receives the action. +In contrast to `withLatestFrom` that immediately subscribes whether the action is dispatched yet or not. If that state used by the selector is not initialized yet, you could get an error that you're not expecting. + +Examples of **incorrect** code for this rule: + +```ts +class Effect { + detail$ = createEffect(() => { + return this.actions.pipe( + ofType(ProductDetailPage.loaded), + // ⚠ + withLatestFrom(this.store.select(selectProducts)), + mergeMap(([action, products]) => { + ... + }) + ) + }) +} +``` + +Examples of **correct** code for this rule: + +```ts +class Effect { + detail$ = createEffect(() => { + return this.actions.pipe( + ofType(ProductDetailPage.loaded), + concatLatestFrom(() => this.store.select(selectProducts)), + mergeMap(([action, products]) => { + ... + }) + ) + }) +} +``` + +## Rule Config + +To configure this rule you can use the `strict` option. +The default is `false`. + +To always report the uses of `withLatestFrom` use: + +```json +"rules": { + "@ngrx/prefer-concat-latest-from": ["warn", { "strict": true }] +} +``` + +To report only needed uses of `withLatestFrom` use: + +```json +"rules": { + "@ngrx/prefer-concat-latest-from": ["warn", { "strict": false }] +} +``` + +## Further reading + +- [`concatLatestFrom` API](api/operators/concatLatestFrom) +- [Incorporating State](guide/effects#incorporating-state) + + +# prefer-effect-callback-in-block-statement + +A block statement is easier to troubleshoot. + +- **Type**: suggestion +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +This rule prefers that the callback of an effect is a block statement. +This makes it easier to troubleshoot type errors, for when example an RxJS operator isn't imported. + +Examples of **incorrect** code for this rule: + +```ts +class Effect { + effectNOK = createEffect(() => + this.actions.pipe( + ofType(detailsLoaded), + concatMap(() => ...), + ) + ) +} +``` + +Examples of **correct** code for this rule: + +```ts +class Effect { + effectOK = createEffect(() => { + return this.actions.pipe( + ofType(detailsLoaded), + concatMap(() => ...), + ) + }) +} +``` + +## Further reading + +- https://github.com/ngrx/platform/issues/2192 + + +# prefer-inline-action-props + +Prefer using inline types instead of interfaces, types or classes. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +Lots of actions in an NgRx codebase have props, and we need to define the props type of an action when the action is defined. It might seem better to use named interfaces or types while defining those types, but in reality, it will obscure their meaning to the developer using them. Actions props are essentially like function arguments, and the function caller needs to know exactly what type of data to provide (which results in a better IDE experience). + +Note: some property names are not allowed to be used, such as `type` + +Examples of **incorrect** code for this rule: + +```ts +export interface User { + id: number; + fullName: string; +} +export const addUser = createAction( + '[Users] Add User', + props() +); +``` + +Examples of **correct** code for this rule: + +```ts +export const addUser = createAction( + '[Users] Add User', + props<{ id: number; fullName: string }>() +); +// or +export const addUser = createAction( + '[Users] Add User', + props<{ user: User }>() +); +``` + + +# prefer-one-generic-in-create-for-feature-selector + +Prefer using a single generic to define the feature state. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +`createFeatureSelector` is typically used with `forFeature`, which should not be aware about the shape of the Global Store. Most of the time, feature states are lazy-loaded. As such, they only need to know (and care) about their own shape. + +This doesn't affect the [composability of these selectors](https://timdeschryver.dev/blog/sharing-data-between-modules-is-peanuts) across features. +You can still use multiple selectors from different feature states together. + +> Tip: If you're accessing a lazy loaded feature that isn't loaded yet, the state returned by `createFeatureSelector` is `undefined`. + +Examples of **incorrect** code for this rule: + +```ts +const customersFeatureState = createFeatureSelector< + GlobalStore, + CustomersFeatureState +>('customers'); +``` + +Examples of **correct** code for this rule: + +```ts +const customersFeatureState = + createFeatureSelector('customers'); +``` + + +# prefer-protected-state + +A Signal Store prefers protected state + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +This rule ensures that state changes are only managed by the Signal Store to prevent unintended modifications and provide clear ownership of where changes occur. + +Examples of **incorrect** code for this rule: + +```ts +const Store = signalStore({ protectedState: false }, withState({})); +``` + +Examples of **correct** code for this rule: + +```ts +const Store = signalStore(withState({})); +``` + +```ts +const Store = signalStore({ protectedState: true }, withState({})); +``` + + +# prefer-selector-in-select + +Using a selector in the `select` is preferred over `string` or `props drilling`. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +It's recommended to use selectors to get data out of the state tree. +A selector is memoized, thus this has the benefit that it's faster because the result is cached and will only be recalculated when it's needed. + +Because a selector is just a pure function, it's easier to test. + +Examples of **incorrect** code for this rule: + +```ts +// ⚠ Usage of strings to select state slices +this.store.select('customers'); +this.store.pipe(select('customers')); + +// ⚠ Usage of props drilling to select state slices +this.store.select((state) => state.customers); +this.store.pipe(select((state) => state.customers)); +``` + +Examples of **correct** code for this rule: + +```ts +import * as fromCustomers from '@customers/selectors'; + +this.store.select(fromCustomers.selectAllCustomers); +this.store.pipe(select(fromCustomers.selectAllCustomers)); +``` + +## Further reading + +- [Selector docs](guide/store/selectors) +- [Testing selectors docs](guide/store/testing#testing-selectors) + + +# prefix-selectors-with-select + +The selector should start with "select", for example "selectEntity". + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: No + + + + +It's recommended prefixing selector function names with the word "select" combined with a description of the value being selected. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +// ⚠ Usage of a selector without any prefix +export const feature = createSelector( + (state: AppState) => state.feature +); + +// ⚠ Usage of a selector without any description +export const select = (id: string) => + createSelector((state: AppState) => state.feature); + +// ⚠ Usage of a selector with a `get` prefix +export const getFeature: MemoizedSelector = ( + state: AppState +) => state.feature; + +// ⚠ Usage of a selector with improper casing +const selectfeature = createFeatureSelector( + featureKey +); + +// ⚠ Usage of a `createSelectorFactory` without `select` prefix +const createSelector = createSelectorFactory((projectionFun) => + defaultMemoize( + projectionFun, + orderDoesNotMatterComparer, + orderDoesNotMatterComparer + ) +); +``` + +Examples of **correct** code for this rule: + +```ts +export const selectFeature = createSelector( + (state: AppState) => state.feature +); + +export const selectFeature: MemoizedSelector = ( + state: AppState +) => state.feature; + +const selectFeature = createFeatureSelector(featureKey); + +export const selectThing = (id: string) => + createSelector(selectThings, (things) => things[id]); +``` + +## Further reading + +- [Redux Style Guide](https://redux.js.org/style-guide/style-guide#name-selector-functions-as-selectthing) + + +# require-super-ondestroy + +Overriden ngOnDestroy method in component stores require a call to super.ngOnDestroy(). + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +This rule enforces that any class which inherits the `ComponentStore` class and overrides the `ngOnDestroy` lifecycle hook must include a call to `super.ngOnDestroy()`. This ensures proper cleanup of resources managed by the `ComponentStore` class. + +Example of **incorrect** code for this rule: + +```ts +@Injectable() +export class BooksStore + extends ComponentStore + implements OnDestroy +{ + // ... other BooksStore class members + + override ngOnDestroy(): void { + this.cleanUp(); // custom cleanup logic + } +} +``` + +Example of **correct** code for this rule: + +```ts +@Injectable() +export class BooksStore + extends ComponentStore + implements OnDestroy +{ + // ... other BooksStore class members + + override ngOnDestroy(): void { + this.cleanUp(); + super.ngOnDestroy(); + } +} +``` + + +# select-style + +Selector can be used either with `select` as a pipeable operator or as a method. + +- **Type**: suggestion +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: Yes + + + + +## Rule Details + +There are two ways of selecting data from the store, either by using the `this.store.select(selectorFn)` method, or by using the `this.store.pipe(select(selectorFn))` operator. Either way is considered correct, although the first way is preferred as it requires less code and it doesn't require the need to import the `selector` operator. + +Because it's important to keep things consistent, this rule disallows using both across the same codebase. + +Examples of **incorrect** code for this rule: + +```ts +export class Component { + someData$ = this.store.select(someData); + otherData$ = this.store.pipe(select(otherData)); +} +``` + +Examples of **correct** code for this rule: + +```ts +export class Component { + someData$ = this.store.select(someData); + otherData$ = this.store.select(otherData); +} +``` + +## Rule Config + +To configure this rule you can change the preferred `mode` of the selectors, the allowed values are `method` and `operator`. +The default is `method`. + +To prefer the **method** syntax (`this.store.select(selector)`) use: + +```json +"rules": { + "@ngrx/select-style": ["warn", "method"] +} +``` + +To prefer the **operator** syntax (`this.store.pipe(select(selector))`) use: + +```json +"rules": { + "@ngrx/select-style": ["warn", "operator"] +} +``` + + +# signal-state-no-arrays-at-root-level + +signalState should accept a record or dictionary as an input argument. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +This rule ensure that a Signal State shouldn't accept an array type at the root level. + +Examples of **correct** code for this rule: + +```ts +const store = withState({ foo: 'bar' }); + +const store = withState({ arrayAsProperty: ['foo', 'bar'] }); + +const initialState = {}; +const store = signalStore(withState(initialState)); +``` + +Examples of **incorrect** code for this rule: + +```ts +const store = withState([1, 2, 3]); + +const store = withState([{ foo: 'bar' }]); + +const store = withState([]); + +const initialState = []; +const store = withState(initialState); +``` + + +# signal-store-feature-should-use-generic-type + +A custom Signal Store feature that accepts an input should define a generic type. + +- **Type**: problem +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +This rule ensure that a Signal Store feature uses a generic type to define the feature state. + +Examples of **incorrect** code for this rule: + +```ts +const withY = () => + signalStoreFeature({ state: type<{ y: number }>() }, withState({})); +``` + +```ts +const withY = () => { + return signalStoreFeature( + type<{ state: { y: number } }>(), + withState({}) + ); +}; +``` + +```ts +function withY() { + return signalStoreFeature( + type<{ state: { y: number } }>(), + withState({}) + ); +} +``` + +Examples of **correct** code for this rule: + +```ts +const withY = () => + signalStoreFeature({ state: type<{ y: Y }>() }, withState({})); +``` + +```ts +const withY = <_>() => { + return signalStoreFeature( + type<{ state: { y: number } }>(), + withState({}) + ); +}; +``` + +```ts +function withY<_>() { + return signalStoreFeature( + { state: type<{ y: Y }>() }, + withState({}) + ); +} +``` + +## Further reading + +- [Known TypeScript Issues with Custom Store Features](guide/signals/signal-store/custom-store-features#known-typescript-issues) + + +# updater-explicit-return-type + +`Updater` should have an explicit return type. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +To enforce that the `updater` method from `@ngrx/component-store` returns the expected state interface, we must explicitly add the return type. + +Examples of **incorrect** code for this rule: + +```ts +interface MoviesState { + movies: Movie[]; +} + +class MoviesStore extends ComponentStore { + readonly addMovie = this.updater((state, movie: Movie) => ({ + movies: [...state.movies, movie], + // ⚠ this doesn't throw, but is caught by the linter + extra: 'property', + })); +} +``` + +Examples of **correct** code for this rule: + +```ts +interface MoviesState { + movies: Movie[]; +} + +class MoviesStore extends ComponentStore { + readonly addMovie = this.updater( + (state, movie: Movie): MoviesState => ({ + movies: [...state.movies, movie], + // ⚠ this does throw + extra: 'property', + }) + ); +} +``` + + +# use-consistent-global-store-name + +Use a consistent name for the global store. + +- **Type**: suggestion +- **Fixable**: No +- **Suggestion**: Yes +- **Requires type checking**: No +- **Configurable**: Yes + + + + +## Rule Details + +The name of the global store should be used consistent. + +Examples of **incorrect** code for this rule: + +```ts +export class ClassOne { + constructor(private store: Store) {} +} + +export class ClassTwo { + constructor(private customersStore: Store) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +export class ClassOne { + constructor(private store: Store) {} +} + +export class ClassTwo { + constructor(private store: Store) {} +} +``` + +## Rule Config + +To configure this rule you can change the preferred `name` of the global store. +The default is `store`. + +To change the name of the global store use: + +```json +"rules": { + "@ngrx/use-consistent-global-store-name": ["warn", "store$"] +} +``` + + +# use-effects-lifecycle-interface + +Ensures classes implement lifecycle interfaces corresponding to the declared lifecycle methods. + +- **Type**: suggestion +- **Fixable**: Yes +- **Suggestion**: No +- **Requires type checking**: No +- **Configurable**: No + + + + +## Rule Details + +If a class is using an effect lifecycle hook, it should implement the corresponding interface. +This prevents signature typos, and it's safer if the signature changes in the future. + +Examples of **incorrect** code for this rule: + +```ts +class Effect { + ngrxOnInitEffects(): Action { + return { type: '[Effect] Init' }; + } +} +``` + +```ts +class Effect { + constructor(private actions$: Actions) {} + + ngrxOnRunEffects(resolvedEffects$: Observable) { + return this.actions$.pipe( + ofType('LOGGED_IN'), + exhaustMap(() => + resolvedEffects$.pipe( + takeUntil(this.actions$.pipe(ofType('LOGGED_OUT'))) + ) + ) + ); + } +} +``` + +Examples of **correct** code for this rule: + +```ts +import { OnInitEffects } from '@ngrx/effects'; + +class Effect implements OnInitEffects { + ngrxOnInitEffects(): Action { + return { type: '[Effect] Init' }; + } +} +``` + +```ts +import { OnRunEffects } from '@ngrx/effects'; + +class Effect implements OnRunEffects { + constructor(private actions$: Actions) {} + ngrxOnRunEffects(resolvedEffects$: Observable) { + return this.actions$.pipe( + ofType('LOGGED_IN'), + exhaustMap(() => + resolvedEffects$.pipe( + takeUntil(this.actions$.pipe(ofType('LOGGED_OUT'))) + ) + ) + ); + } +} +``` + +## Further reading + +- [Effect lifecycle docs](guide/effects/lifecycle#controlling-effects) + + +# with-state-no-arrays-at-root-level + +withState should accept a record or dictionary as an input argument. + +- **Type**: problem +- **Fixable**: No +- **Suggestion**: No +- **Requires type checking**: Yes +- **Configurable**: No + + + + + +# V10 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@10 +``` + +## Dependencies + +Version 10 has the minimum version requirements: + +- Angular version 10.x +- Angular CLI version 10.x +- TypeScript version 3.9.x +- RxJS version 6.5.x + +## Breaking changes + +### @ngrx/effects + +#### Stricter Effects typing + +Returning an `EMPTY` observable without `{ dispatch: false }` now produces a type error. + +BEFORE: + +```ts +someEffect$ = createEffect(() => EMPTY); +``` + +AFTER: + +```ts +someEffect$ = createEffect(() => EMPTY, { dispatch: false }); +``` + +### @ngrx/schematics + +#### `skipTest` option is removed + +The `skipTest` option is renamed to `skipTests`. + +BEFORE: + +```bash +ng generate container UsersPage --skipTest +``` + +AFTER: + +```bash +ng generate container UsersPage --skipTests +``` + + +# V11 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@11 +``` + +## Dependencies + +Version 11 has the minimum version requirements: + +- Angular version 11.x +- Angular CLI version 11.x +- TypeScript version 4.0.x +- RxJS version 6.5.x + +## Breaking changes + +### @ngrx/store + +#### Stricter `props` for `createAction` + +`createAction` doesn't allow `{}` as a `props` type anymore, because `{}` is compatible with primitive types. + +BEFORE: + +```ts +const customerPageLoaded = createAction( + '[Customer Page] Loaded', + props<{}>() +); + +customerPageLoaded({}); // βœ”οΈ +customerPageLoaded({ foo: 'bar' }); // βœ”οΈ +customerPageLoaded(0); // πŸ‘ˆ no error +customerPageLoaded(false); // πŸ‘ˆ no error +``` + +AFTER: + +```ts +const customerPageLoaded = createAction( + '[Customer Page] Loaded', + props>() +); +``` + +#### Renames + +- the interface `Props` is renamed to `ActionCreatorProps` +- the interface `On` is renamed to `ReducerTypes` + +### @ngrx/entity + +#### Removed `addAll` in favor of `setAll` + +To overwrite the entities, we previously used the `addAll` method but the method name was confusing. + +BEFORE: + +```ts +adapter.addAll(action.entities, state); +``` + +AFTER: + +The new method name `setAll` describes the intention better. + +```ts +adapter.setAll(action.entities, state); +``` + +### @ngrx/router-store + +#### Optimized `selectQueryParams`, `selectQueryParam` and `selectFragment` selectors + +They select query parameters/fragment from the root router state node instead of the last router state node. + +BEFORE: + +```ts +const queryParams$ = this.store.select(selectQueryParams); +const fragment$ = this.store.select(selectFragment); + +/* +router state: +{ + root: { + queryParams: { + search: 'foo', + }, + fragment: 'bar', + firstChild: { + queryParams: { + search: 'foo', πŸ‘ˆ query parameters are selected from here + }, + fragment: 'bar', πŸ‘ˆ fragment is selected from here + firstChild: undefined, + }, + }, + url: '/books?search=foo#bar', +} +*/ +``` + +AFTER: + +```ts +const queryParams$ = this.store.select(selectQueryParams); +const fragment$ = this.store.select(selectFragment); + +/* +router state: +{ + root: { + queryParams: { + search: 'foo', πŸ‘ˆ query parameters are selected from here + }, + fragment: 'bar', πŸ‘ˆ fragment is selected from here + firstChild: { + queryParams: { + search: 'foo', + }, + fragment: 'bar', + firstChild: undefined, + }, + }, + url: '/books?search=foo#bar', +} +*/ +``` + +### @ngrx/store-devtools + +#### Error handling + +The error handler now receives the full `Error` object instead of the error stack when an error occurs while computing the state. + +## Deprecations + +### @ngrx/effects + +#### The Effect decorator + +The Effect decorator, `@Effect`, is deprecated in favor for the `createEffect` method. + +See the [docs](/guide/effects#writing-effects) for more info. + +BEFORE: + +```ts +@Effect() +login$ = this.actions$.pipe(...); +``` + +AFTER: + +```ts +login$ = createEffect(() => { + return this.actions$.pipe(...); +}); +``` + +To automatically migrate `@Effect` usages to the `createEffect` method, run the following NgRx migration (this migration is only available in v11 and v12): + +```sh +ng generate @ngrx/schematics:create-effect-migration +``` + + +# V12 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@12 +``` + +## Dependencies + +Version 12 has the minimum version requirements: + +- Angular version 12.x +- Angular CLI version 12.x +- TypeScript version 4.2.x +- RxJS version 6.5.x + +## Deprecations + +### @ngrx/store + +#### Selectors With Props + +Selectors with props are deprecated in favor of "normal" factory selectors. +Factory selectors have the following benefits: + +- easier to write and to teach +- selectors are typed +- child selectors are correctly memoized + +BEFORE: + +```ts +const selectCustomer = createSelector( + selectCustomers, + (customers, props: { customerId: number }) => { + return customers[props.customerId]; + } +); + +// Or if the selector is already defined as a factory selector + +const selectCustomer = () => + createSelector( + selectCustomers, + (customers, props: { customerId: number }) => { + return customers[props.customerId]; + } + ); +``` + +AFTER: + +```ts +const selectCustomer = (customerId: number) => + createSelector(selectCustomers, (customers) => { + return customers[customerId]; + }); +``` + + +# V13 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@13 +``` + +## Dependencies + +Version 13 has the minimum version requirements: + +- Angular version 13.x +- Angular CLI version 13.x +- TypeScript version 4.4.x +- RxJS version 6.5.x, 6.6.x, or 7.4.x + +## Breaking changes + +### @ngrx/store + +#### Variadic tuple types for createSelector + +The `createSelector` can now be combined with an unlimited amount of child selectors, which was previously limited to 8 child selectors. + + + +A migration is provided to update the generic types of createSelector. + + + +BEFORE: + +```ts +const selector = createSelector< + State, + Customer, + Order[], + CustomerWithOrder +>; +``` + +AFTER: + +```ts +// needs to be a tuple πŸ‘‡ +const selector = createSelector< + State, + [Customer, Order[]], + CustomerWithOrder +>; +``` + +#### Action creator props + +The `props` of an action created with `createAction` can't be a primitive type (`string`, `number`, `boolean`). +Additionally, the error messages are updated to be more explicit about what is wrong. + +BEFORE: + +```ts +const action = createAction('[Source] Event', props()); +``` + +AFTER: + +```ts +const action = createAction( + '[Source] Event', + props<{ name: string }>() +); +``` + +#### StoreModule.forFeature with FeatureSlice + +The `StoreModule.forFeature()` method doesn't accept a configuration object anymore. + +BEFORE: + +```ts +StoreModule.forFeature(featureSlice, { + initialState: 100, + metaReducers: [metaReducer], +}); + +StoreModule.forFeature( + { name: 'feature', reducer: featureReducer }, + { initialState: 100, metaReducers: [metaReducer] } +); +``` + +AFTER: + +```ts +StoreModule.forFeature(featureSlice); + +StoreModule.forFeature({ name: 'feature', reducer: featureReducer }); +``` + +#### StoreModule initialState config + +The `initialState` provided via the configuration object is now typed and needs to match the interface of the state interface. + +#### Testing: Reset mock store + +Mock stores are not reset automagically after each test. + +To restore the previous behavior you add add your own reset logic in an `afterEach` hook. +Note that this only applicable when the Angular `TestBed` isn't teared down by Angular, for more info see the [ModuleTeardownOptions options](https://angular.dev/api/core/testing/ModuleTeardownOptions). + +When using Jasmine, reset the mock store after each test by adding the following to the `test.ts`: + +```ts +import { getTestBed } from '@angular/core/testing'; +import { MockStore } from '@ngrx/store/testing'; + +afterEach(() => { + getTestBed().inject(MockStore, null)?.resetSelectors(); +}); +``` + +When using Jest, reset the mock store after each test by using a `afterEach` hook: + +```ts +import { MockStore } from '@ngrx/store/testing'; + +afterEach(() => { + TestBed.inject(MockStore)?.resetSelectors(); +}); +``` + +### @ngrx/schematics + +#### create-effect-migration + +The `create-effect-migration` migration has been removed. +It's now added to the automated migration (run with `ng-update`) of the `@ngrx/effects` module. +This replaces all references to `@Effect` (which is deprecated) with the `createEffect` method. + +### @ngrx/data + +The `queryManySuccess` query sets the `loaded` flag to `true`. + +### @ngrx/component + +#### PushPipe + +`PushPipe` no longer has a class-level generic type parameter. +This change is needed to make `PushPipe` work with strict templates. +It affects the use of `PushPipe` outside of component templates. + +## Deprecations + +### @ngrx/store + +#### createFeatureSelector + +The `createFeatureSelector` method with two generics, has been deprecated. +Instead, only provide the `FeatureState` generic. + + + +A migration is provided to update the generic types of `createFeatureSelector`. + + + +BEFORE: + +```ts +const selectFeature = createFeatureSelector( + 'feature' +); +``` + +AFTER: + +```ts +const selectFeature = createFeatureSelector('feature'); +``` + +#### Selectors With Props + +Selectors with props are deprecated in favor of "normal" factory selectors. +Factory selectors have the following benefits: + +- easier to write and to teach +- selectors are typed +- child selectors are correctly memoized + +BEFORE: + +```ts +const selectCustomer = createSelector( + selectCustomers, + (customers, props: { customerId: number }) => { + return customers[props.customerId]; + } +); + +// Or if the selector is already defined as a factory selector + +const selectCustomer = () => + createSelector( + selectCustomers, + (customers, props: { customerId: number }) => { + return customers[props.customerId]; + } + ); +``` + +AFTER: + +```ts +const selectCustomer = (customerId: number) => + createSelector(selectCustomers, (customers) => { + return customers[customerId]; + }); +``` + +### @ngrx/effects + +#### @Effect decorator + +The `@Effect` decorator is deprecated in favor of `createEffect`. +See the [docs](/guide/effects#writing-effects) for more info. + + + +A migration is provided to update to remove the `@Effect` decorator, and to wrap the effect within a `createEffect` method. + + + + +# V14 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@14 +``` + +## Dependencies + +Version 14 has the minimum version requirements: + +- Angular version 14.x +- Angular CLI version 14.x +- TypeScript version 4.6.x +- RxJS version ^6.5.3 || ^7.5.0 + +## Breaking changes + +### @ngrx/router-store + +#### Default serialized and serializer names + +- Renamed `DefaultRouterStateSerializer` to `FullRouterStateSerializer` (used with `RouterState.Full`) +- `MinimalRouterStateSerializer` is the default serializer (used with `RouterState.Minimal`) + + + +A migration is provided to rename `DefaultRouterStateSerializer` to `FullRouterStateSerializer`. + + + +### @ngrx/component + +#### Use global render strategy in zone-less mode + +The native local rendering strategy is replaced by global in zone-less mode for better performance. + +BEFORE: + +The change detection is triggered via `changeDetectorRef.detectChanges` in zone-less mode. + +AFTER: + +The change detection is triggered via `Ι΅markDirty` in zone-less mode. + +#### Add error as value to `LetDirective`'s context + +The `$error` property from `LetDirective`'s view context is a thrown error or `undefined` instead of `true`/`false`. + +BEFORE: + +```html +

    {{ e }}

    +``` + +- `e` will be `true` when `obs$` emits error event. +- `e` will be `false` when `obs$` emits next/complete event. + +AFTER: + +```html +

    {{ e }}

    +``` + +- `e` will be thrown error when `obs$` emits error event. +- `e` will be `undefined` when `obs$` emits next/complete event. + +#### Add ability to pass non-observable values + +1. The context of `LetDirective` is strongly typed when `null` or + `undefined` is passed as input. + +BEFORE: + +```html +

    {{ n }}

    +

    {{ u }}

    +``` + +- The type of `n` is `any`. +- The type of `u` is `any`. + +AFTER: + +```html +

    {{ n }}

    +

    {{ u }}

    +``` + +- The type of `n` is `null`. +- The type of `u` is `undefined`. + +--- + +2. Arrays, iterables, generator functions, and readable streams are + not treated as observable-like inputs anymore. To keep the same behavior + as in v13, convert the array/iterable/generator function/readable stream + to observable using the `from` function from the `rxjs` package + before passing it to the `LetDirective`/`PushPipe`. + +BEFORE: + +```ts +@Component({ + template: ` +

    {{ n }}

    +

    {{ numbers | ngrxPush }}

    + `, +}) +export class NumbersComponent { + numbers = [1, 2, 3]; +} +``` + +AFTER: + +```ts +@Component({ + template: ` +

    {{ n }}

    +

    {{ numbers$ | ngrxPush }}

    + `, +}) +export class NumbersComponent { + numbers$ = from([1, 2, 3]); +} +``` + +### @ngrx/schematics + +#### Remove `defaultCollection` + +Removed the `defaultCollection` option in favor of `schematicCollections`. +When the schematics are installed, `@ngrx/schematics` is added to the `schematicCollections` in the `angular.json` file. + + + +A migration is provided to add `@ngrx/schematics` to the `schematicCollections`. + + + +## Deprecations + +### @ngrx/component + +#### ReactiveComponentModule + +`ReactiveComponentModule` is deprecated in favor of `LetModule` and `PushModule`. + +BEFORE: + +```ts +import { ReactiveComponentModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + ReactiveComponentModule, + ], +}) +export class MyFeatureModule {} +``` + +AFTER: + +If the components declared in the `MyFeatureModule` use only the `*ngrxLet` directive: + +```ts +import { LetModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetModule, + ], +}) +export class MyFeatureModule {} +``` + +If the components declared in the `MyFeatureModule` use only the `ngrxPush` pipe: + +```ts +import { PushModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + PushModule, + ], +}) +export class MyFeatureModule {} +``` + +If the components declared in the `MyFeatureModule` use both the `*ngrxLet` directive and the `ngrxPush` pipe: + +```ts +import { LetModule, PushModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetModule, + PushModule, + ], +}) +export class MyFeatureModule {} +``` + + +# V15 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@15 +``` + +## Dependencies + +Version 15 has the minimum version requirements: + +- Angular version 15.x +- Angular CLI version 15.x +- TypeScript version 4.8.x +- RxJS version ^6.5.3 || ^7.5.0 + +## Breaking changes + +### @ngrx/store + +#### Strict Selector Projector + +The projector function on the selector is type-safe by default. +The projector function also has become strict for selectors with props. + +BEFORE: + +The projector is not type-safe, allowing for potential mismatch types in the projector function. + +```ts +const mySelector = createSelector( + () => 'one', + () => 2, + (one, two) => 3 +); + +mySelector.projector(); // <- type is projector(...args: any[]): number +``` + +AFTER: + +The projector is strict by default, but can be bypassed with an `any` type assertion to specify a less specific type. + +```ts +const mySelector = createSelector( + () => 'one', + () => 2, + (one, two) => 3 +); + +mySelector.projector(); // <- Results in type error. Type is projector(s1: string, s2: number): number +``` + +To retain previous behavior + +```ts +const mySelector = createSelector( + () => 'one', + () => 2, + (one, two) => 3 +)(mySelector.projector as any)(); +``` + +### @ngrx/effects + +#### Removal of @Effect + +The `@Effect` decorator is removed in favor of [`createEffect`](api/effects/createEffect). +This change also means that the ESLint rules @ngrx/no-effect-decorator-and-creator and @ngrx/no-effect-decorator are removed. + +> A migration was added (in the v13 release) to upgrade existing codebases to the `createEffect` function. + +BEFORE: + +An effect is defined with the `@Effect` decorator. + +```ts +@Effect() +data$ = this.actions$.pipe(); +``` + +AFTER: + +You need to define an effect with `createEffect`. + +```ts +data$ = createEffect(() => this.actions$.pipe()); +``` + +### @ngrx/router-store + +#### Title + +BREAKING CHANGE: + +The property `title: string | undefined` is added to the `MinimalActivatedRouteSnapshot` interface when a title added to the route config. + +BEFORE: + +The `MinimalActivatedRouteSnapshot` interface doesn't contain the `title` property. + +AFTER: + +The `MinimalActivatedRouteSnapshot` interface contains the required `title` property. + +### @ngrx/component + +#### Removal of ReactiveComponentModule + +The `ReactiveComponentModule` is removed in favor of `LetModule` and `PushModule`. + +BEFORE: + +```ts +import { ReactiveComponentModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + ReactiveComponentModule, + ], +}) +export class MyFeatureModule {} +``` + +AFTER: + +```ts +import { LetModule, PushModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetModule, + PushModule, + ], +}) +export class MyFeatureModule {} +``` + +#### Renamed LetViewContext Properties + +The `$` prefix is removed from `LetViewContext` property names. + +BEFORE: + +`LetViewContext` property names contain the `$` prefix: + +```html + + ... + +``` + +AFTER: + +`LetViewContext` property names don't contain the `$` prefix: + +```html + + ... + +``` + +#### LetDirective Behavior on Suspense Event + +The `LetDirective` view will be cleared when the replaced observable is in a suspense state. +Also, the `suspense` property is removed from the `LetViewContext` because it would always be `false` when the `LetDirective` view is rendered. +Instead of `suspense` property, use [suspense template](guide/component/let#using-suspense-template) to handle the suspense state. + +BEFORE: + +The `LetDirective` view will not be cleared when the replaced observable is in a suspense state and the suspense template is not passed: + +```ts +@Component({ + template: ` + + + +

    {{ o }}

    + + `, +}) +export class TestComponent { + obs$ = of(1); + + replaceObs(): void { + this.obs$ = of(2).pipe(delay(1000)); + } +} +``` + +AFTER: + +The `LetDirective` view will be cleared when the replaced observable is in a suspense state and the suspense template is not passed: + +```ts +@Component({ + template: ` + + + +

    {{ o }}

    + + `, +}) +export class TestComponent { + obs$ = of(1); + + replaceObs(): void { + this.obs$ = of(2).pipe(delay(1000)); + } +} +``` + +## Deprecations + +### @ngrx/store + +#### Deprecated `createFeature` Signature with Root State (Introduced in v15.2) + +The `createFeature` signature with root state is deprecated in favor of a signature without root state. + +BEFORE: + +```ts +interface AppState { + users: State; +} + +export const usersFeature = createFeature({ + name: 'users', + reducer: createReducer(initialState /* case reducers */), +}); +``` + +AFTER: + +```ts +export const usersFeature = createFeature({ + name: 'users', + reducer: createReducer(initialState /* case reducers */), +}); +``` + +#### Deprecated `getMockStore` in favor of `createMockStore` (Introduced in v15.4) + +BEFORE: + +```ts +import { getMockStore } from '@ngrx/store/testing'; +const mockStore = getMockStore(); +``` + +AFTER: + +```ts +import { createMockStore } from '@ngrx/store/testing'; +const mockStore = createMockStore(); +``` + +### @ngrx/router-store + +#### Renamed `getSelectors` Function (Introduced in v15.2) + +The `getSelectors` function is deprecated in favor of `getRouterSelectors`. + +BEFORE: + +```ts +import { getSelectors } from '@ngrx/router-store'; + +const routerSelectors = getSelectors(); +``` + +AFTER: + +```ts +import { getRouterSelectors } from '@ngrx/router-store'; + +const routerSelectors = getRouterSelectors(); +``` + + +# V16 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@16 +``` + +## Dependencies + +Version 16 has the minimum version requirements: + +- Angular version 16.x +- Angular CLI version 16.x +- TypeScript version 5.x +- RxJS version ^6.5.x || ^7.5.0 + +## Breaking changes + +### @ngrx/store + +#### Preserve the event name case with createActionGroup + +The event name case is preserved when converting to the action name by using the createActionGroup function. + +BEFORE: + +All letters of the event name will be lowercase, except for the initial letters of words starting from the second word, which will be uppercase. + +```ts +const authApiActions = createActionGroup({ + source: 'Auth API', + events: { + 'LogIn Success': emptyProps(), + 'login failure': emptyProps(), + 'Logout Success': emptyProps(), + logoutFailure: emptyProps(), + }, +}); + +// generated actions: +const { loginSuccess, loginFailure, logoutSuccess, logoutfailure } = + authApiActions; +``` + +AFTER: + +The initial letter of the first word of the event name will be lowercase, and the initial letters of the other words will be uppercase. The case of other letters in the event name will remain the same. + +```ts +const authApiActions = createActionGroup({ + source: 'Auth API', + events: { + 'LogIn Success': emptyProps(), + 'login failure': emptyProps(), + 'Logout Success': emptyProps(), + logoutFailure: emptyProps(), + }, +}); + +// generated actions: +const { logInSuccess, loginFailure, logoutSuccess, logoutFailure } = + authApiActions; +``` + +#### Strongly typed `createFeature` selectors + +Projectors of selectors generated by createFeature are strongly typed. + +BEFORE: + +Projector function arguments of selectors generated by createFeature are not strongly typed: + +```ts +const counterFeature = createFeature({ + name: 'counter', + reducer: createReducer({ count: 0 }), +}); + +counterFeature.selectCount.projector; +// ^ type: (...args: any[]) => number +``` + +AFTER: + +Projector function arguments of selectors generated by createFeature are strongly typed: + +```ts +const counterFeature = createFeature({ + name: 'counter', + reducer: createReducer({ count: 0 }), +}); + +counterFeature.selectCount.projector; +// ^ type: (featureState: { count: number; }) => number +``` + +#### Remove `createFeature` signature with root state + +The `createFeature` signature with root state is removed in favor of a signature without root state. +An automatic migration is added to remove this signature. + +BEFORE: + +```ts +interface AppState { + users: State; +} + +export const usersFeature = createFeature({ + name: 'users', + reducer: createReducer(initialState /* case reducers */), +}); +``` + +AFTER: + +```ts +export const usersFeature = createFeature({ + name: 'users', + reducer: createReducer(initialState /* case reducers */), +}); +``` + +#### Replace `getMockStore` with `createMockStore` + +The `getMockStore` function is replaced in favor of `createMockStore`. +An automatic migration is added to rename the function. + +BEFORE: + +```ts +import { getMockStore } from '@ngrx/store/testing'; +const mockStore = getMockStore(); +``` + +AFTER: + +```ts +import { createMockStore } from '@ngrx/store/testing'; +const mockStore = createMockStore(); +``` + +### @ngrx/router-store + +#### Replace `getSelectors` with `getRouterSelectors` + +The `getSelectors` function is replaced in favor of `getRouterSelectors`. +An automatic migration is added to rename the function. + +BEFORE: + +```ts +import { getSelectors } from '@ngrx/router-store'; + +const routerSelectors = getSelectors(); +``` + +AFTER: + +```ts +import { getRouterSelectors } from '@ngrx/router-store'; + +const routerSelectors = getRouterSelectors(); +``` + +### @ngrx/schematics + +#### Replace `any` types with `unknown` type + +NgRx schematics do not use `any` types to define actions, these are replaced with the `unknown` type. + +## Deprecations + +### @ngrx/component + +#### LetModule + +`LetModule` is deprecated in favor of the standalone `LetDirective`. + +BEFORE: + +```ts +import { LetModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetModule, + ], +}) +export class MyFeatureModule {} +``` + +```ts +import { Component } from '@angular/core'; +import { LetModule } from '@ngrx/component'; + +@Component({ + // ... other metadata + standalone: true, + imports: [ + // ... other imports + LetModule, + ], +}) +export class MyStandaloneComponent {} +``` + +AFTER: + +```ts +import { LetDirective } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetDirective, + ], +}) +export class MyFeatureModule {} +``` + +```ts +import { Component } from '@angular/core'; +import { LetDirective } from '@ngrx/component'; + +@Component({ + // ... other metadata + standalone: true, + imports: [ + // ... other imports + LetDirective, + ], +}) +export class MyStandaloneComponent {} +``` + +#### PushModule + +`PushModule` is deprecated in favor of the standalone `PushPipe`. + +BEFORE: + +```ts +import { PushModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + PushModule, + ], +}) +export class MyFeatureModule {} +``` + +```ts +import { Component } from '@angular/core'; +import { PushModule } from '@ngrx/component'; + +@Component({ + // ... other metadata + standalone: true, + imports: [ + // ... other imports + PushModule, + ], +}) +export class MyStandaloneComponent {} +``` + +AFTER: + +```ts +import { PushPipe } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + PushPipe, + ], +}) +export class MyFeatureModule {} +``` + +```ts +import { Component } from '@angular/core'; +import { PushPipe } from '@ngrx/component'; + +@Component({ + // ... other metadata + standalone: true, + imports: [ + // ... other imports + PushPipe, + ], +}) +export class MyStandaloneComponent {} +``` + + +# V17 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@17 +``` + +## Dependencies + +Version 17 has the minimum version requirements: + +- Angular version 17.x +- Angular CLI version 17.x +- TypeScript version 5.x +- RxJS version ^6.5.x || ^7.5.0 + +## Breaking changes + +### @ngrx/entity + +#### `adapter.getSelectors` return a `MemoizedSelector` type + +Selectors returned by the `adapter.getSelectors` signature that accepts a parent selector are strongly typed. + +BEFORE: + +```ts +const { + selectIds, // type: (state: object) => string[] | number[] + selectEntities, // type: (state: object) => Dictionary + selectAll, // type: (state: object) => Book[] + selectTotal, // type: (state: object) => number +} = adapter.getSelectors(selectBooksState); +``` + +AFTER: + +```ts +const { + selectIds, // type: MemoizedSelector + selectEntities, // type: MemoizedSelector> + selectAll, // type: MemoizedSelector + selectTotal, // type: MemoizedSelector +} = adapter.getSelectors(selectBooksState); +``` + +## @ngrx/store-devtools + +### Rename of `connectOutsideZone` to `connectInZone` + +The DevTools extension connection is established outside of the Angular zone. +To revert to the previous behavior, set the `connectInZone` config property to `true`. +An automatic migration is added to configure the DevTools to the original behavior. + +BEFORE: + +The DevTools extension connection is established in the Angular zone. +To change this behavior the config `connectOutsideZone` can be set to `true`. + +```ts +provideStoreDevtools({ + connectOutsideZone: true +}), +``` + +AFTER: + +The DevTools extension connection is established outside of the Angular zone. +To change this behavior the config `connectInZone` can be set to `true`. + +```ts +provideStoreDevtools({ + connectInZone: true +}), +``` + +## @ngrx/component + +### Removal of `LetModule` + +The `LetModule` is removed in favor of the standalone `LetDirective`. + +BEFORE: + +```ts +import { LetModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + LetModule, + ], +}) +export class MyFeatureModule {} +``` + +AFTER: + +```ts +import { Component } from '@angular/core'; +import { LetDirective } from '@ngrx/component'; + +@Component({ + // ... other metadata + standalone: true, + imports: [ + // ... other imports + LetDirective, + ], +}) +export class MyStandaloneComponent {} +``` + +### Removal of `PushModule` + +The `PushModule` is deprecated in favor of the standalone `PushPipe`. + +BEFORE: + +```ts +import { PushModule } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + PushModule, + ], +}) +export class MyFeatureModule {} +``` + +AFTER: + +```ts +import { PushPipe } from '@ngrx/component'; + +@NgModule({ + imports: [ + // ... other imports + PushPipe, + ], +}) +export class MyFeatureModule {} +``` + + +# V18 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@18 +``` + +## Dependencies + +Version 18 has the minimum version requirements: + +- Angular version 18.x +- Angular CLI version 18.x +- TypeScript version 5.4.x +- RxJS version ^6.5.x || ^7.5.0 + +## Breaking changes + +### @ngrx/store + +#### Merge `Action` and `TypedAction` interfaces + +The Action and TypedAction interfaces are merged into one interface. +This change has an ng-update schematic that automatically updates your code. + +BEFORE: + +There was a separation between the Action and TypedAction interfaces. + +AFTER: + +The Action interface accepts a generic type parameter that represents the payload type (defaults to string). +The TypedAction interface is removed. + +### @ngrx/effects + +#### Remove `concatLatestFrom` operator + +The `concatLatestFrom` operator has been removed from `@ngrx/effects` in favor of the `@ngrx/operators` package. +This change has an ng-update schematic that automatically updates your code. + +BEFORE: + +```ts +import { concatLatestFrom } from '@ngrx/effects'; +``` + +AFTER: + +```ts +import { concatLatestFrom } from '@ngrx/operators'; +``` + +### @ngrx/component-store + +#### Remove `tapResponse` operator + +The `tapResponse` operator has been removed from `@ngrx/component-store` in favor of the `@ngrx/operators` package. +This change has an ng-update schematic that automatically updates your code. + +BEFORE: + +```ts +import { tapResponse } from '@ngrx/component-store'; +``` + +AFTER: + +```ts +import { tapResponse } from '@ngrx/operators'; +``` + +### @ngrx/router-store + +#### Include `string[]` as return type for `selectQueryParam` + +The Angular router can return an array of query parameters. The `selectQueryParam` selector now includes `string[]` as a possible return type. + +BEFORE: + +The return type of `selectQueryParam` is `MemoizedSelector`.I + +AFTER: + +The return type of `selectQueryParam` now includes `string[]`, making the return type `MemoizedSelector`. + +### @ngrx/eslint-plugin + +#### Simplify configs + +The rules have been regrouped into preconfigured configigurations to make it easier to find and configure them. +This change has been done to improve the developer experience while using ESLint v9. +The new configuration can be found in the documentation at https://ngrx.io/guide/eslint-plugin. + +BEFORE: + +Rules were grouped for a package, strict-mode, with(out) type-checking. + +AFTER: + +Rules are grouped per package. The differences were to small to further divide the rules in configurations. +If needed, you can always disable a rule. + + +# V19 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@19 +``` + +## Dependencies + +Version 19 has the minimum version requirements: + +- Angular version 19.x +- Angular CLI version 19.x +- TypeScript version 5.6.x +- RxJS version ^6.5.x || ^7.5.0 + +## Breaking changes + +### Signals + +#### `computed` is replaced with `props` + +To support more cases, the `props` property is added to `signalStoreFeature`, which replaces the existing `computed` property. +This change has an ng-update schematic that automatically updates your code. + +- The `computed` property in the `SignalStoreFeature` type is renamed to `props`. +- The `computed` property in the `signalStoreFeature` method is renamed to `props`. +- The `EntityComputed` and `NamedEntityComputed` types in the `entities` plugin are renamed to `EntityProps` and `NamedEntityProps`. + +BEFORE: + +```ts +import { computed, Signal } from '@angular/core'; +import { + signalStoreFeature, + SignalStoreFeature, + type, + withComputed, +} from '@ngrx/signals'; +import { EntityComputed } from '@ngrx/signals/entities'; + +export function withTotalEntities(): SignalStoreFeature< + { state: {}; computed: EntityComputed; methods: {} }, + { state: {}; computed: { total: Signal }; methods: {} } +> { + return signalStoreFeature( + { computed: type>() }, + withComputed(({ entities }) => ({ + total: computed(() => entities().length), + })) + ); +} +``` + +AFTER: + +```ts +import { computed, Signal } from '@angular/core'; +import { + signalStoreFeature, + SignalStoreFeature, + type, + withComputed, +} from '@ngrx/signals'; +import { EntityProps } from '@ngrx/signals/entities'; + +export function withTotalEntities(): SignalStoreFeature< + { state: {}; props: EntityProps; methods: {} }, + { state: {}; props: { total: Signal }; methods: {} } +> { + return signalStoreFeature( + { props: type>() }, + withComputed(({ entities }) => ({ + total: computed(() => entities().length), + })) + ); +} +``` + +#### Rename `rxMethod.unsubscribe()` to `rxMethod.destroy()` + +The `unsubscribe` method from `rxMethod` is renamed to `destroy`. + +BEFORE: + +```ts +const logNumber = rxMethod(tap(console.log)); + +const num1Ref = logNumber(interval(1_000)); +const num2Ref = logNumber(interval(2_000)); + +// destroy `num1Ref` after 2 seconds +setTimeout(() => num1Ref.unsubscribe(), 2_000); + +// destroy all reactive method refs after 5 seconds +setTimeout(() => logNumber.unsubscribe(), 5_000); +``` + +AFTER: + +```ts +const logNumber = rxMethod(tap(console.log)); + +const num1Ref = logNumber(interval(1_000)); +const num2Ref = logNumber(interval(2_000)); + +// destroy `num1Ref` after 2 seconds +setTimeout(() => num1Ref.destroy(), 2_000); + +// destroy all reactive method refs after 5 seconds +setTimeout(() => logNumber.destroy(), 5_000); +``` + +### Schematics + +#### Standalone is the default + +(Container) components created by the Container Schematic are now standalone by default. + + +# V20 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +# NgRx Store related packages +ng update @ngrx/store@20.0.0-beta.0 + +# NgRx Signals package +ng update @ngrx/signals@20.0.0-beta.0 +``` + +## Dependencies + +Version 20 has the minimum version requirements: + +- Angular version 20.x +- Angular CLI version 20.x +- TypeScript version 5.8.x +- RxJS version ^6.5.x || ^7.5.0 + +## Breaking changes + +### Signals + +The internal `STATE_SOURCE` is no longer represented as a single `WritableSignal` holding the entire state object. Instead, each top-level state property becomes its own `WritableSignal` or remains as-is if a `WritableSignal` is provided as a state property. + +BEFORE: + +1. The initial state object reference is preserved: + +```ts +const initialState = { ngrx: 'rocks' }; + +// signalState: +const state = signalState(initialState); +state() === initialState; // true + +// withState: +const Store = signalStore(withState(initialState)); +const store = new Store(); +getState(store) === initialState; // true +``` + +2. Root state properties can be added dynamically: + +```ts +// signalState: +const state = signalState>({}); +console.log(state()); // {} + +patchState(state, { ngrx: 'rocks' }); +console.log(state()); // { ngrx: 'rocks' } + +// withState: +const Store = signalStore( + { protectedState: false }, + withState>({}) +); +const store = new Store(); +console.log(getState(store)); // {} + +patchState(store, { ngrx: 'rocks' }); +console.log(getState(store)); // { ngrx: 'rocks' } +``` + +AFTER: + +1. The initial state object reference is not preserved: + +```ts +const initialState = { ngrx: 'rocks' }; + +// signalState: +const state = signalState(initialState); +state() === initialState; // false + +// withState: +const Store = signalStore(withState(initialState)); +const store = new Store(); +getState(store) === initialState; // false +``` + +2. Root state properties can not be added dynamically: + +```ts +// signalState: +const state = signalState>({}); +console.log(state()); // {} + +patchState(state, { ngrx: 'rocks' }); +console.log(state()); // {} + +// withState: +const Store = signalStore( + { protectedState: false }, + withState>({}) +); +const store = new Store(); +console.log(getState(store)); // {} + +patchState(store, { ngrx: 'rocks' }); +console.log(getState(store)); // {} +``` + +### Entity + +#### `getInitialState` is type-safe + +`getInitialState` is now type-safe, meaning that the initial state must match the entity state type. This change ensures that the initial state is correctly typed and prevents additional properties from being added to the state. + +BEFORE: + +```ts +import { + EntityState, + createEntityAdapter, + EntityAdapter, +} from '@ngrx/entity'; + +interface Book { + id: string; + title: string; +} +interface BookState extends EntityState { + selectedBookId: string | null; +} + +export const adapter: EntityAdapter = + createEntityAdapter(); +export const initialState: BookState = adapter.getInitialState({ + selectedBookId: '1', + otherProperty: 'value', +}); +``` + +AFTER: + +```ts +import { + EntityState, + createEntityAdapter, + EntityAdapter, +} from '@ngrx/entity'; + +interface Book { + id: string; + title: string; +} +interface BookState extends EntityState { + selectedBookId: string | null; +} + +export const adapter: EntityAdapter = + createEntityAdapter(); +export const initialState: BookState = adapter.getInitialState({ + selectedBookId: '1', + // πŸ‘‡ this throws an error + // otherProperty: 'value', +}); +``` + +### Effects + +#### `act` operator is removed + +The `act` operator is removed in favor of core RxJS flattening operators and `mapResponse` from the `@ngrx/operators` package. + +BEFORE: + +```ts +@Injectable() +export class AuthEffects { + readonly login$ = createEffect(() => { + return this.actions$.pipe( + ofType(LoginPageActions.loginFormSubmitted), + act({ + project: ({ credentials }) => + this.authService + .login(credentials) + .pipe( + map((user) => AuthApiActions.loginSuccess({ user })) + ), + error: (error) => AuthApiActions.loginFailure({ error }), + operator: exhaustMap, + }) + ); + }); +} +``` + +AFTER: + +```ts +@Injectable() +export class AuthEffects { + readonly login$ = createEffect(() => { + return this.actions$.pipe( + ofType(LoginPageActions.loginFormSubmitted), + exhaustMap(({ credentials }) => + this.authService.login(credentials).pipe( + mapResponse({ + next: (user) => AuthApiActions.loginSuccess({ user }), + error: (error) => AuthApiActions.loginFailure({ error }), + }) + ) + ) + ); + }); +} +``` + + +# V4 Update Guide + +## Dependencies + +You need to have the latest versions of TypeScript and RxJS to use NgRx version 4 libraries. + +TypeScript 2.4.x +RxJS 5.4.x + +## @ngrx/core + +`@ngrx/core` is no longer needed and conflicts with @ngrx/store. Remove the dependency from your project. + +BEFORE: + +```ts +import { compose } from '@ngrx/core/compose'; +``` + +AFTER: + +```ts +import { compose } from '@ngrx/store'; +``` + +## @ngrx/store + +### Action interface + +The `payload` property has been removed from the `Action` interface. It was a source of type-safety +issues, especially when used with `@ngrx/effects`. If your interface/class has a payload, you need to provide +the type. + +BEFORE: + +```ts +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Effect, Actions } from '@ngrx/effects'; + +@Injectable() +export class MyEffects { + @Effect() + someEffect$: Observable = this.actions$ + .ofType(UserActions.LOGIN) + .pipe( + map((action) => action.payload), + map(() => new AnotherAction()) + ); + + constructor(private actions$: Actions) {} +} +``` + +AFTER: + +```ts +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Effect, Actions } from '@ngrx/effects'; +import { Login } from '../actions/auth'; + +@Injectable() +export class MyEffects { + @Effect() + someEffect$: Observable = this.actions$ + .ofType(UserActions.LOGIN) + .pipe( + map((action) => action.payload), + map(() => new AnotherAction()) + ); + + constructor(private actions$: Actions) {} +} +``` + +If you prefer to keep the `payload` interface property, you can provide your own parameterized version. + +```ts +export interface ActionWithPayload extends Action { + payload: T; +} +``` + +And if you need an unsafe version to help with transition. + +```ts +export interface UnsafeAction extends Action { + payload?: any; +} +``` + +### Registering Reducers + +Previously to be AOT compatible, it was required to pass a function to the `provideStore` method to compose the reducers into one root reducer. The `initialState` was also provided to the method as an object in the second argument. + +BEFORE: + +`reducers/index.ts` + +```ts +const reducers = { + auth: fromAuth.reducer, + layout: fromLayout.reducer, +}; + +const rootReducer = combineReducers(reducers); + +export function reducer(state: any, action: any) { + return rootReducer(state, action); +} +``` + +`app.module.ts` + +```ts +import { StoreModule } from '@ngrx/store'; +import { reducer } from './reducers'; + +@NgModule({ + imports: [ + StoreModule.provideStore(reducer, { + auth: { + loggedIn: true, + }, + }), + ], +}) +export class AppModule {} +``` + +This has been simplified to only require a map of reducers that will be composed together by the library. A second argument is a configuration object where you provide the `initialState`. + +AFTER: + +`reducers/index.ts` + +```ts +import { ActionReducerMap } from '@ngrx/store'; + +export interface State { + auth: fromAuth.State; + layout: fromLayout.State; +} + +export const reducers: ActionReducerMap = { + auth: fromAuth.reducer, + layout: fromLayout.reducer, +}; +``` + +`app.module.ts` + +```ts +import { StoreModule } from '@ngrx/store'; +import { reducers } from './reducers'; + +@NgModule({ + imports: [ + StoreModule.forRoot(reducers, { + initialState: { + auth: { + loggedIn: true, + }, + }, + }), + ], +}) +export class AppModule {} +``` + +## @ngrx/effects + +### Registering Effects + +BEFORE: + +`app.module.ts` + +```ts +@NgModule({ + imports: [EffectsModule.run(SourceA), EffectsModule.run(SourceB)], +}) +export class AppModule {} +``` + +AFTER: + +The `EffectsModule.forRoot` method is _required_ in your root `AppModule`. Provide an empty array +if you don't need to register any root-level effects. + +`app.module.ts` + +```ts +@NgModule({ + imports: [EffectsModule.forRoot([SourceA, SourceB, SourceC])], +}) +export class AppModule {} +``` + +Import `EffectsModule.forFeature` in any NgModule, whether be the `AppModule` or a feature module. + +`feature.module.ts` + +```ts +@NgModule({ + imports: [ + EffectsModule.forFeature([ + FeatureSourceA, + FeatureSourceB, + FeatureSourceC, + ]), + ], +}) +export class FeatureModule {} +``` + +### Init Action + +The `@ngrx/store/init` action now fires prior to effects starting. Use defer() for the same behaviour. + +BEFORE: + +`app.effects.ts` + +```ts +import { Dispatcher, Action } from '@ngrx/store'; +import { Actions, Effect } from '@ngrx/effects'; + +import * as auth from '../actions/auth.actions'; + +@Injectable() +export class AppEffects { + @Effect() + init$: Observable = this.actions$ + .ofType(Dispatcher.INIT) + .switchMap((action) => { + return of(new auth.LoginAction()); + }); + + constructor(private actions$: Actions) {} +} +``` + +AFTER: + +`app.effects.ts` + +```ts +import { Action } from '@ngrx/store'; +import { Actions, Effect } from '@ngrx/effects'; +import { defer } from 'rxjs'; + +import * as auth from '../actions/auth.actions'; + +@Injectable() +export class AppEffects { + @Effect() + init$: Observable = defer(() => { + return of(new auth.LoginAction()); + }); + + constructor(private actions$: Actions) {} +} +``` + +### Testing Effects + +BEFORE: + +```ts +import { + EffectsTestingModule, + EffectsRunner, +} from '@ngrx/effects/testing'; +import { MyEffects } from './my-effects'; + +describe('My Effects', () => { + let effects: MyEffects; + let runner: EffectsRunner; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [EffectsTestingModule], + providers: [ + MyEffects, + // other providers + ], + }); + + effects = TestBed.inject(MyEffects); + runner = TestBed.inject(EffectsRunner); + }); + + it('should work', () => { + runner.queue(SomeAction); + + effects.someSource$.subscribe((result) => { + expect(result).toBe(AnotherAction); + }); + }); +}); +``` + +AFTER: + +```ts +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { hot, cold } from 'jasmine-marbles'; +import { MyEffects } from './my-effects'; +import { ReplaySubject } from 'rxjs/ReplaySubject'; + +describe('My Effects', () => { + let effects: MyEffects; + let actions: Observable; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + MyEffects, + provideMockActions(() => actions), + // other providers + ], + }); + + effects = TestBed.inject(MyEffects); + }); + + it('should work', () => { + actions = hot('--a-', { a: SomeAction, ... }); + + const expected = cold('--b', { b: AnotherAction }); + + expect(effects.someSource$).toBeObservable(expected); + }); + + it('should work also', () => { + actions = new ReplaySubject(1); + + actions.next(SomeAction); + + effects.someSource$.subscribe(result => { + expect(result).toBe(AnotherAction); + }); + }); +}); +``` + +## @ngrx/router-store + +### Registering the module + +BEFORE: + +`reducers/index.ts` + +```ts +import * as fromRouter from '@ngrx/router-store'; + +export interface State { + router: fromRouter.RouterState; +} + +const reducers = { + router: fromRouter.routerReducer, +}; + +const rootReducer = combineReducers(reducers); + +export function reducer(state: any, action: any) { + return rootReducer(state, action); +} +``` + +`app.module.ts` + +```ts +import { RouterModule } from '@angular/router'; +import { RouterStoreModule } from '@ngrx/router-store'; +import { reducer } from './reducers'; + +@NgModule({ + imports: [ + StoreModule.provideStore(reducer), + RouterModule.forRoot([ + // some routes + ]) + RouterStoreModule.connectRouter() + ] +}) +export class AppModule {} +``` + +AFTER: + +`reducers/index.ts` + +```ts +import * as fromRouter from '@ngrx/router-store'; + +export interface State { + routerReducer: fromRouter.RouterReducerState; +} + +export const reducers = { + routerReducer: fromRouter.routerReducer, +}; +``` + +`app.module.ts` + +```ts +import { StoreRouterConnectingModule } from '@ngrx/router-store'; +import { reducers } from './reducers'; + +@NgModule({ + imports: [ + StoreModule.forRoot(reducers), + RouterModule.forRoot([ + // some routes + ]), + StoreRouterConnectingModule, + ], +}) +export class AppModule {} +``` + +### Navigation actions + +Navigation actions are not provided as part of the V4 package. You provide your own +custom navigation actions that use the `Router` within effects to navigate. + +BEFORE: + +```ts +import { go, back, forward } from '@ngrx/router-store'; + +store.dispatch( + go(['/path', { routeParam: 1 }], { page: 1 }, { replaceUrl: false }) +); + +store.dispatch(back()); + +store.dispatch(forward()); +``` + +AFTER: + +```ts +import { Action } from '@ngrx/store'; +import { NavigationExtras } from '@angular/router'; + +export const GO = '[Router] Go'; +export const BACK = '[Router] Back'; +export const FORWARD = '[Router] Forward'; + +export class Go implements Action { + readonly type = GO; + + constructor( + public payload: { + path: any[]; + query?: object; + extras?: NavigationExtras; + } + ) {} +} + +export class Back implements Action { + readonly type = BACK; +} + +export class Forward implements Action { + readonly type = FORWARD; +} + +export type Actions = Go | Back | Forward; +``` + +```ts +import * as RouterActions from './actions/router'; + +store.dispatch(new RouterActions.Go({ + path: ['/path', { routeParam: 1 }], + query: { page: 1 }, + extras: { replaceUrl: false } +}); + +store.dispatch(new RouterActions.Back()); + +store.dispatch(new RouterActions.Forward()); +``` + +```ts +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { Location } from '@angular/common'; +import { Effect, Actions } from '@ngrx/effects'; +import { map, tap } from 'rxjs/operators'; +import * as RouterActions from './actions/router'; + +@Injectable() +export class RouterEffects { + @Effect({ dispatch: false }) + navigate$ = this.actions$.ofType(RouterActions.GO).pipe( + map((action: RouterActions.Go) => action.payload), + tap(({ path, query: queryParams, extras }) => + this.router.navigate(path, { queryParams, ...extras }) + ) + ); + + @Effect({ dispatch: false }) + navigateBack$ = this.actions$ + .ofType(RouterActions.BACK) + .do(() => this.location.back()); + + @Effect({ dispatch: false }) + navigateForward$ = this.actions$ + .ofType(RouterActions.FORWARD) + .do(() => this.location.forward()); + + constructor( + private actions$: Actions, + private router: Router, + private location: Location + ) {} +} +``` + +## @ngrx/store-devtools + +### Instrumentation method + +**NOTE:** store-devtools currently causes severe performance problems when +used with router-store. We are working to +[fix this](https://github.com/ngrx/platform/issues/97), but for now, avoid +using them together. + +BEFORE: + +`app.module.ts` + +```ts +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; + +@NgModule({ + imports: [ + StoreDevtoolsModule.instrumentStore({ maxAge: 50 }), + // OR + StoreDevtoolsModule.instrumentOnlyWithExtension({ + maxAge: 50, + }), + ], +}) +export class AppModule {} +``` + +AFTER: + +`app.module.ts` + +```ts +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; +import { environment } from '../environments/environment'; // Angular CLI environment + +@NgModule({ + imports: [ + !environment.production + ? StoreDevtoolsModule.instrument({ maxAge: 50 }) + : [], + ], +}) +export class AppModule {} +``` + + +# V7 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@7 +``` + +## Dependencies + +V7 has the minimum version requirements: + +- Angular version 7 +- TypeScript version 3.1.x +- RxJS version 6.x + +## @ngrx/store + +### Feature loaded action + +When multiple feature reducers are registered, a single action is dispatched instead of an action for each added feature reducer. + +BEFORE: + +When adding/removing one feature: + +```ts +{type: '@ngrx/store/update-reducers', feature: 'feat1'} +``` + +When adding/removing multiple features: + +```ts +{type: '@ngrx/store/update-reducers', feature: 'feat1'} +{type: '@ngrx/store/update-reducers', feature: 'feat2'} +``` + +AFTER: + +When adding/removing one feature: + +```ts +{type: '@ngrx/store/update-reducers', features: ['feat1']} +``` + +When adding/removing multiple features: + +```ts +{type: '@ngrx/store/update-reducers', features: ['feat1', 'feat2']} +``` + +## @ngrx/effects + +### `ofType` removal + +In NgRx 6.1 the `ofType` function was marked as deprecated in favor of the `ofType` operator, in NgRx v7 this function was dropped. + +BEFORE: + +```ts +import { Effect, Actions } from '@ngrx/effects'; + +@Injectable() +export class MyEffects { + @Effect() + someEffect$: Observable = this.actions$ + .ofType(UserActions.LOGIN) + .pipe(map(() => new AnotherAction())); + + constructor(private actions$: Actions) {} +} +``` + +AFTER: + +```ts +import { Effect, Actions, ofType } from '@ngrx/effects'; // import ofType operator + +@Injectable() +export class MyEffects { + @Effect() + someEffect$: Observable = this.actions$.pipe( + ofType(UserActions.LOGIN), // use the pipeable ofType operator + map(() => new AnotherAction()) + ); + + constructor(private actions$: Actions) {} +} +``` + +## @ngrx/router-store + +### Default state key + +The default NgRx router state key is changed from `routerReducer` to `router`. + +BEFORE: + +```ts +StoreRouterConnectingModule.forRoot({ + stateKey: 'router', +}), +``` + +AFTER: + +```ts +StoreRouterConnectingModule.forRoot(), +``` + +### ActivatedRouteSnapshot.RouteConfig + +The default router serializer now returns a `null` value for `routeConfig` when `routeConfig` doesn't exist on the `ActivatedRouteSnapshot` instead of an empty object. + +BEFORE: + +```json +{ + "routeConfig": {} +} +``` + +AFTER: + +```json +{ + "routeConfig": null +} +``` + +## @ngrx/store-devtools + +### Recompute state action + +The devtools is using the new `@ngrx/store-devtools/recompute` action to recompute its state instead of the `@ngrx/store/update-reducers` action. + + +# V8 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@8 +``` + +## Dependencies + +V8 has the minimum version requirements: + +- Angular version 8.x +- Angular CLI version 8.0.2 +- TypeScript version 3.4.x +- RxJS version 6.4.0 + +## Breaking changes + +### @ngrx/store + +#### `META_REDUCERS` token + + + +A migration is provided to rename the `META_REDUCERS` token to `USER_PROVIDED_META_REDUCERS` + + + +The `META_REDUCERS` token has been renamed to `USER_PROVIDED_META_REDUCERS`. + +The `META_REDUCERS` token has become a multi token and can be used by +library authors. + +#### Selectors with only a projector function aren't valid anymore + +This change will make the usage of selectors more consistent. + +BEFORE: + +```ts +const getTodosById = createSelector( + (state: TodoAppSchema, id: number) => + state.todos.find((p) => p.id === id) +); +``` + +AFTER: + +```ts +const getTodosById = createSelector( + (state: TodoAppSchema) => state.todos, + (todos: Todo[], id: number) => todos.find((p) => p.id === id) +); +``` + +#### MemoizedSelector enforces the return type to strictly match the second generic type. + +For example, prior to 8.0.0 the following would be fine, since the return type `boolean` is widened to `boolean | null`. + +BEFORE: + +```ts +export const getLoginPagePending: MemoizedSelector< + State, + boolean | null +> = createSelector( + selectLoginPageState, + (loginState) => loginState.pending // boolean +); +``` + +Now this will produce an error: + +```txt + error TS2322: Type 'MemoizedSelector' is not assignable to type 'MemoizedSelector'. + Types of property 'setResult' are incompatible. + Type '(result?: boolean | undefined) => void' is not assignable to type '(result?: boolean | null | undefined) => void'. + Types of parameters 'result' and 'result' are incompatible. + Type 'boolean | null | undefined' is not assignable to type 'boolean | undefined'. + Type 'null' is not assignable to type 'boolean | undefined'. +``` + +Fix would be to specify the type correctly. + +AFTER: + +```ts +export const getLoginPagePending: MemoizedSelector = + createSelector( + selectLoginPageState, + (loginState) => loginState.pending // boolean + ); +``` + +Another interesting case is when object literals are returned, e.g. + +BEFORE: + +```ts +interface Reaction { + happy: boolean; + tweet: string; +} +export const getNews: MemoizedSelector = + createSelector(newsState, (news) => { + if (news.isFake) { + return { + happy: false, + tweet: 'blah blah blah', + }; + } + return { + happy: true, + tweet: 'anyway', + }; + }); +``` + +Now the error message would happen (and it is a bit cryptic): + +```txt +Type 'MemoizedSelector' is not assignable to type 'MemoizedSelector'. + Type 'Reaction' is not assignable to type '{ happy: false; tweet: string; } | { happy: true; tweet: string; }'. + Type 'Reaction' is not assignable to type '{ happy: true; tweet: string; }'. + Types of property 'happy' are incompatible. + Type 'boolean' is not assignable to type 'true'.ts(2322) +``` + +Fix would be to add the return type to the `projector` function + +AFTER: + +```ts +export const getNews: MemoizedSelector = + createSelector(newsState, (news): Reaction => { + if (news.isFake) { + return { + happy: false, + tweet: 'blah blah blah', + }; + } + return { + happy: true, + tweet: 'anyway', + }; + }); +``` + +#### Return type of `createSelectorFactory` and `createSelector` + +The return type of the `createSelectorFactory` and `createSelector` functions are now a `MemoizedSelector` instead of a `Selector`. + +#### Deprecation of ngrx-store-freeze + + + +A migration is provided to remove the usage `ngrx-store-freeze`, remove it from the `package.json`, and to enable the built-in runtime checks `strictStateImmutability` and `strictActionImmutability`. + + + +With the new built-in runtime checks, the usage of the `ngrx-store-freeze` package has become obsolete. + +### @ngrx/effects + +#### Resubscribe on Errors + +If an error occurs (or is flattened) in the main effect's pipe then it will be +reported and the effect is resubscribed automatically. In cases when this new behavior is +undesirable, it can be disabled using `{resubscribeOnError: false}` within the effect metadata +(for each effect individually). [Learn more](/guide/effects/lifecycle#resubscribe-on-error). + +BEFORE: + +```ts +login$ = createEffect(() => + this.actions$.pipe( + ofType(LoginPageActions.login), + exhaustMap((action) => + this.authService.login(action.credentials).pipe( + map((user) => AuthApiActions.loginSuccess({ user })), + catchError((error) => + of(AuthApiActions.loginFailure({ error })) + ) + ) + ) + ) +); +``` + +AFTER: + +```ts +logins$ = createEffect( + () => + this.actions$.pipe( + ofType(LoginPageActions.login), + exhaustMap((action) => + this.authService.login(action.credentials).pipe( + map((user) => AuthApiActions.loginSuccess({ user })), + catchError((error) => + of(AuthApiActions.loginFailure({ error })) + ) + ) + ) + ), + { resubscribeOnError: false } +); +``` + +### @ngrx/router-store + +#### Required usage of `forRoot` + + + +A migration is provided and will append `forRoot` to `StoreRouterConnectingModule` + + + +Usage of `forRoot` is now required for `StoreRouterConnectingModule` + +BEFORE: + +```ts +@NgModule({ + imports: [StoreRouterConnectingModule], +}) +export class AppModule {} +``` + +AFTER: + +```ts +@NgModule({ + imports: [StoreRouterConnectingModule.forRoot()], +}) +export class AppModule {} +``` + +### @ngrx/entity + +#### add undefined to Dictionary's index signature + +`Dictionary` and `DictionaryNum` could be producing `undefined` but previous typings were not explicit about it. + +### @ngrx/store-devtools + +#### `actionsWhitelist` is renamed to `actionsSafelist` + +BEFORE: + +```ts +StoreDevtoolsModule.instrument({ + actionsWhitelist: ['...'], +}); +``` + +AFTER: + +```ts +StoreDevtoolsModule.instrument({ + actionsSafelist: ['...'], +}); +``` + +#### `actionsBlacklist` is renamed to `actionsBlocklist` + +BEFORE: + +```ts +StoreDevtoolsModule.instrument({ + actionsBlacklist: ['...'], +}); +``` + +AFTER: + +```ts +StoreDevtoolsModule.instrument({ + actionsBlocklist: ['...'], +}); +``` + +## @ngrx/data + +### Renames + +To stay consistent with the other `@ngrx/*` packages, the following has been renamed: + +- `NgrxDataModule` is renamed to `EntityDataModule` +- `NgrxDataModuleWithoutEffects` is renamed to `EntityDataModuleWithoutEffects` +- `NgrxDataModuleConfig` is renamed to `EntityDataModuleConfig` + + + +The installation of `@ngrx/data` package via `ng add @ngrx/data` will remove `ngrx-data` from the `package.json` and will also perform these renames. + + + + +# V9 Update Guide + +## Angular CLI update + +NgRx supports using the Angular CLI `ng update` command to update your dependencies. Migration schematics are run to make the upgrade smoother. These schematics will fix some of the breaking changes. + +To update your packages to the latest released version, run the command below. + +```sh +ng update @ngrx/store@9 +``` + +## Dependencies + +Version 9 has the minimum version requirements: + +- Angular version 9.x +- Angular CLI version 9.x +- TypeScript version 3.7.x +- RxJS version 6.5.x + +## Breaking changes + +### @ngrx/store + +#### Immutability checks are turned on by default + +In the previous version of NgRx, [runtime checks](/guide/store/configuration/runtime-checks) were opt-in. +In this version, the immutability runtime check is turned on by default. + +To turn them off use: + +```ts +@NgModule({ + imports: [ + StoreModule.forRoot(reducers, { + runtimeChecks: { + strictStateImmutability: false, + strictActionImmutability: false, + }, + }), + ], +}) +export class AppModule {} +``` + +#### Change default value of minimal to true (Store Schematics) + +This change only registers `StoreModule.forRoot({})` in the provided `module` with an empty array. +Before, it also registered an empty reducer. + +#### Change default value of creators to true (Store Schematics) + +Action and reducers creators are now the default, instead of defining Actions as classes, and reducers as switch-based method. + +### @ngrx/effects + +#### `resubscribeOnError` is renamed to `useEffectsErrorHandler` + +The option `resubscribeOnError` has been renamed to `useEffectsErrorHandler`. +This change is made to make it possible to provide a [custom effect error handler](/guide/effects/lifecycle#customizing-the-effects-error-handler). + +Before: + +```ts +// decorator +@Effect({ resubscribeOnError: false }) +effect$ = ... + +// with createEffect +effect$ = createEffect(() => ..., {resubscribeOnError: false }) +``` + +After: + +```ts +// decorator +@Effect({ useEffectsErrorHandler: false }) +effect$ = ... + +// with createEffect +effect$ = createEffect(() => ..., { useEffectsErrorHandler: false }) +``` + + + +A migration is provided to rename the occurrences. + + + +#### Limit retries to 10 by default + +By default, effects are resubscribed up to 10 errors, previously this was unlimited. + +To change the number, implement a [custom effect error handler](/guide/effects/lifecycle#customizing-the-effects-error-handler), or [change the number](/guide/effects/lifecycle#customizing-the-effects-error-handler) TK add example to docs and provide link here. + +#### Dispatch init actions once + +Previously, when an Effect implemented the `OnInitEffects` lifecycle hook, the defined action would be dispatched after each time a lazy loaded module was loaded with the same Effect class. +Now, the action will only be dispatched after the first module is loaded. + +#### Change default value of minimal to true (Effects Schematics) + +This change only registers `EffectsModule.forRoot()` in the provided `module` with an empty array. +Before, it also registered an empty effect class. + +#### Change default value of creators to true (Effects Schematics) + +Effects creators are now the default, instead of defining Effects with the `@Effect()` decorator. + +### @ngrx/router-store + +#### Minimal router state is the default + +Minimal router state becomes the default, this also means that the [`MinimalRouterStateSerializer`](/guide/router-store/configuration) will be used by default. The minimal only contains state that is serializable, see [`MinimalActivatedRouteSnapshot`](/api/router-store/MinimalActivatedRouteSnapshot) for more info. + +The event on the payload of the dispatched actions will only contain the router event id and the url, instead of the Angular RouterEvent. + + + +A migration is provided and adds the `DefaultRouterStateSerializer` if a serializer isn't set. + + + +## Deprecations + +### @ngrx/entity + +#### `addAll` + +The `addAll` entity adapter method has been deprecated, in favor of `setAll`. +`setAll`'s behavior is identical to `addAll`, but the name covers its intention better. + +BEFORE: + +```ts +adapter.addAll([person1, person2, person3], state); +``` + +AFTER: + +```ts +adapter.setAll([person1, person2, person3], state); +``` + + +# @ngrx/operators + +NgRx Operators is a utility library with frequently used RxJS operators for managing state and side effects. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/operators/install) page. + + +# Operators Installation + +## Installing with `ng add` + +You can install the `@ngrx/operators` to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/operators@latest +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/operators`. +2. Run the package manager to install the added dependency. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/operators --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/operators +``` + + +# Operators + +The operators library provides some useful operators that are frequently +used when managing state and side effects. + +## `concatLatestFrom` + +The `concatLatestFrom` operator functions similarly to `withLatestFrom` with one important difference - it lazily evaluates the provided Observable factory. + +This allows you to utilize the source value when selecting additional sources to concat. + +Additionally, because the factory is not executed until it is needed, it also mitigates the performance impact of creating some kinds of Observables. + +For example, when selecting data from the store with `store.select`, `concatLatestFrom` will prevent the +selector from being evaluated until the source emits a value. + +The `concatLatestFrom` operator takes an Observable factory function that returns an array of Observables, or a single Observable. + + + +The `concatLatestFrom` operator has been moved from `@ngrx/effects` to `@ngrx/operators`. If you're looking for the older documentation (prior to v18), see the [v17 documentation](https://v17.ngrx.io/guide/effects/operators#concatlatestfrom). + + + + + +```ts +import { Injectable } from '@angular/core'; +import { Title } from '@angular/platform-browser'; + +import { map, tap } from 'rxjs/operators'; + +import { + Actions, + concatLatestFrom, + createEffect, + ofType, +} from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { routerNavigatedAction } from '@ngrx/router-store'; + +import { selectRouteData } from './router.selectors'; + +@Injectable() +export class RouterEffects { + updateTitle$ = createEffect( + () => + this.actions$.pipe( + ofType(routerNavigatedAction), + concatLatestFrom(() => this.store.select(selectRouteData)), + map(([, data]) => `Book Collection - ${data['title']}`), + tap((title) => this.titleService.setTitle(title)) + ), + { + dispatch: false, + } + ); + + constructor( + private actions$: Actions, + private store: Store, + private titleService: Title + ) {} +} +``` + + + +## tapResponse + +An easy way to handle the response with an Observable in a safe way, without additional boilerplate is to use the `tapResponse` operator. It enforces that the error case is handled and that the effect would still be running should an error occur. It is essentially a simple wrapper around two operators: + +- `tap` that handles success and error cases. +- `catchError(() => EMPTY)` that ensures that the effect continues to run after the error. + + + +The `tapResponse` operator has been moved from `@ngrx/component-store` to `@ngrx/operators`. If you're looking for the older documentation (prior to v18), see the [v17 documentation](https://v17.ngrx.io/guide/component-store/effect#tapresponse). + + + + + +```ts + readonly getMovie = this.effect((movieId$: Observable) => { + return movieId$.pipe( + // πŸ‘‡ Handle race condition with the proper choice of the flattening operator. + switchMap((id) => this.moviesService.fetchMovie(id).pipe( + //πŸ‘‡ Act on the result within inner pipe. + tapResponse( + (movie) => this.addMovie(movie), + (error: HttpErrorResponse) => this.logError(error), + ), + )), + ); + }); +``` + + + +There is also another signature of the `tapResponse` operator that accepts the observer object as an input argument. In addition to the `next` and `error` callbacks, it provides the ability to pass `complete` and/or `finalize` callbacks: + + + +```ts + readonly getMoviesByQuery = this.effect((query$) => { + return query$.pipe( + tap(() => this.patchState({ loading: true }), + switchMap((query) => + this.moviesService.fetchMoviesByQuery(query).pipe( + tapResponse({ + next: (movies) => this.patchState({ movies }), + error: (error: HttpErrorResponse) => this.logError(error), + finalize: () => this.patchState({ loading: false }), + }) + ) + ) + ); + }); +``` + + + +## mapResponse + +The `mapResponse` operator is particularly useful in scenarios where you need to transform data and handle potential errors with minimal boilerplate. + +In the example below, we use `mapResponse` within an NgRx effect to handle loading movies from an API. It demonstrates how to map successful API responses to an action indicating success, and how to handle errors by dispatching an error action. + + + +```ts +export const loadMovies = createEffect( + ( + actions$ = inject(Actions), + moviesService = inject(MoviesService) + ) => { + return actions$.pipe( + ofType(MoviesPageActions.opened), + exhaustMap(() => + moviesService.getAll().pipe( + mapResponse({ + next: (movies) => + MoviesApiActions.moviesLoadedSuccess({ movies }), + error: (error: { message: string }) => + MoviesApiActions.moviesLoadedFailure({ + errorMsg: error.message, + }), + }) + ) + ) + ); + }, + { functional: true } +); +``` + + + + +# Router Actions + +Router Store provides five navigation actions which are dispatched in a specific order. The `routerReducer` provided by Router Store updates its state with the latest router state given by the actions. By default we recommend to use the creator functions we provide. + +## Actions + +### routerRequestAction + +At the start of each navigation, the router will dispatch a `ROUTER_REQUEST` action. + +### routerNavigationAction + +During navigation, before any guards or resolvers run, the router will dispatch a `ROUTER_NAVIGATION` action. + +If you want the `ROUTER_NAVIGATION` to be dispatched after guards or resolvers run, change the Navigation Action Timing. + +### routerNavigatedAction + +After a successful navigation, the router will dispatch a `ROUTER_NAVIGATED` action. + +### routerCancelAction + +When the navigation is cancelled, for example due to a guard saying that the user cannot access the requested page, the router will dispatch a `ROUTER_CANCEL` action. + +The action contains the store state before the navigation. You can use it to restore the consistency of the store. + +### routerErrorAction + +When there is an error during navigation, the router will dispatch a `ROUTER_ERROR` action. + +The action contains the store state before the navigation. You can use it to restore the consistency of the store. + + + +**Note:** You can also still use the action type, which was the previously defined way before action creators were introduced in NgRx. If you are looking for examples of the action types, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/router-store/actions). + + + +## Order of actions + +Success case: + +- `ROUTER_REQUEST` +- `ROUTER_NAVIGATION` +- `ROUTER_NAVIGATED` + +Error / Cancel case (with early Navigation Action Timing): + +- `ROUTER_REQUEST` +- `ROUTER_NAVIGATION` +- `ROUTER_CANCEL` / `ROUTER_ERROR` + +Error / Cancel case (with late Navigation Action Timing): + +- `ROUTER_REQUEST` +- `ROUTER_CANCEL` / `ROUTER_ERROR` + + +# Router Store Configuration Options + + + +```ts +interface StoreRouterConfig { + stateKey?: string | Selector>; + serializer?: new (...args: any[]) => RouterStateSerializer; + navigationActionTiming?: NavigationActionTiming; + routerState?: RouterState; +} +``` + + + +- `stateKey`: The name of reducer key, defaults to `router`. It's also possible to provide a selector function. +- `serializer`: How a router snapshot is serialized. Defaults to `MinimalRouterStateSerializer`. See [Custom Router State Serializer](#custom-router-state-serializer) for more information. +- `navigationActionTiming`: When the `ROUTER_NAVIGATION` is dispatched. Defaults to `NavigationActionTiming.PreActivation`. See [Navigation Action Timing](#navigation-action-timing) for more information. +- `routerState`: Set this property to decide which serializer should be used, if none is provided, and the metadata on the dispatched action. + +## Custom Router State Serializer + +During each navigation cycle, a `RouterNavigationAction` is dispatched with a snapshot of the state in its payload, the `RouterStateSnapshot`. The `RouterStateSnapshot` is a large complex structure, containing many pieces of information about the current state and what's rendered by the router. This can cause performance +issues when used with the Store Devtools. In most cases, you may only need a piece of information from the `RouterStateSnapshot`. In order to pare down the `RouterStateSnapshot` provided during navigation, you provide a custom serializer for the snapshot to only return what you need to be added to the payload and store. + +Your custom serializer should implement the abstract class `RouterStateSerializer` and return a snapshot which should have an interface extending `BaseRouterStoreState`. + +You then provide the serializer through the config. + +**In a custom serializer ts file** + + + +```ts +import { Params, RouterStateSnapshot } from '@angular/router'; +import { RouterStateSerializer } from '@ngrx/router-store'; + +export interface RouterStateUrl { + url: string; + params: Params; + queryParams: Params; +} + +export class CustomSerializer + implements RouterStateSerializer +{ + serialize(routerState: RouterStateSnapshot): RouterStateUrl { + let route = routerState.root; + + while (route.firstChild) { + route = route.firstChild; + } + + const { + url, + root: { queryParams }, + } = routerState; + const { params } = route; + + // Only return an object including the URL, params and query params + // instead of the entire snapshot + return { url, params, queryParams }; + } +} +``` + + + +**In your root reducer** + + + +```ts +export interface State { + router: RouterReducerState; +} + +export const reducers: ActionReducerMap = { + router: routerReducer, +}; +``` + + + +**In your application config** + + + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideStore } from '@ngrx/store'; +import { + provideRouterStore, + routerReducer, +} from '@ngrx/router-store'; + +import { AppComponent } from './app.component'; +import { CustomSerializer } from './custom-serializer'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter([ + // routes + ]), + provideStore({ + router: routerReducer, + }), + provideRouterStore({ + serializer: CustomSerializer, + }), + ], +}; +``` + + + +## Navigation action timing + +`ROUTER_NAVIGATION` is by default dispatched before any guards or resolvers run. This may not always be ideal, for example if you rely on the action to be dispatched after guards and resolvers successfully ran and the new route will be activated. You can change the dispatch timing by providing the corresponding config: + + + +```ts +provideRouterStore({ + navigationActionTiming: NavigationActionTiming.PostActivation, +}); +``` + + + +## Router State Snapshot + +This property decides which router serializer should be used. If there is a custom serializer provided, it will use the provided serializer. `routerState` also sets the metadata on dispatched `@ngrx/router-store` action. + +### RouterState.Minimal + +`RouterState.Minimal` uses the `MinimalRouterStateSerializer` serializer to serialize the Angular Router's `RouterState` and `RouterEvent`. + +The difference between `FullRouterStateSerializer` and the `MinimalRouterStateSerializer` is that this serializer is fully serializable. To make the state and the actions serializable, the properties `paramMap`, `queryParamMap` and `component` are ignored. + + + +```ts +provideRouterStore({ + routerState: RouterState.Minimal, +}); +``` + + + +### RouterState.Full + +When this property is set to `RouterState.Full`, `@ngrx/router-store` uses the `FullRouterStateSerializer` serializer to serialize the Angular router event. + +The metadata on the action contains the Angular router event, e.g. `NavigationStart` and `RoutesRecognized`. + + + +```ts +provideRouterStore({ + routerState: RouterState.Full, +}); +``` + + + + + +The `FullRouterStateSerializer` cannot be used when [serializability runtime checks](guide/store/configuration/runtime-checks) are enabled. +With serializability runtime checks enabled, the `MinimalRouterStateSerializer` serializer **must** be used. + + + + +# @ngrx/router-store + +Bindings to connect the Angular Router with [Store](guide/store). During each router navigation cycle, multiple [actions](guide/router-store/actions) are dispatched that allow you to listen for changes in the router's state. You can then select data from the state of the router to provide additional information to your application. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/router-store/install) page. + +## Setup + + + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideStore } from '@ngrx/store'; +import { + provideRouterStore, + routerReducer, +} from '@ngrx/router-store'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter([ + // routes + ]), + provideStore({ + router: routerReducer, + }), + provideRouterStore(), + ], +}; +``` + + + + + +An example of the `@ngrx/router-store` setup in module-based applications is available at the [following link](https://v17.ngrx.io/guide/router-store#setup). + + + + +# Router Store Installation + +## Installing with `ng add` + +You can install the Router Store to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/router-store@latest +``` + +### Optional `ng add` flags + +| flag | description | value type | default value | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `--path` | Path to the module that you wish to add the import for the `StoreRouterConnectingModule` to. | `string` | +| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `StoreRouterConnectingModule` to. | `string` | +| `--module` | Name of file containing the module that you wish to add the import for the `StoreRouterConnectingModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string` | `app` | + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/router-store`. +2. Run `npm install` to install those dependencies. +3. By default it adds `provideRouterStore()` to the `ApplicationConfig` in the `app.config.ts` file. If you provided flags then the command will attempt to locate and update the corresponding config found by the flags. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/router-store --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/router-store +``` + + +# Router selectors + +The `getRouterSelectors` method supplied within `@ngrx/router-store` provides functions for selecting common information from the router state. + +The default behavior of `getRouterSelectors` selects the router state for the `router` state key. +If the default router state config is overwritten with a different router state key, the `getRouterSelectors` method takes a selector function to select the piece of state where the router state is being stored. +The example below shows how to provide a selector for the top level `router` key in your state object. + +**Note:** The `getRouterSelectors` method works with the `routerReducer` provided by `@ngrx/router-store`. If you use a [custom serializer](guide/router-store/configuration#custom-router-state-serializer), you'll need to provide your own selectors. + +Usage: + + + +You can see the full example at StackBlitz: + + + +## Creating a Selector for A Single Entity With Id As Route Param + + + + + + + + + + + + + +## Extracting all params in the current route + +The `selectRouteParam{s}` selector extracts params from the **leaf child** route segment only. + +It means that when using nested routes with the Angular router (use of the `children` property), only params from the leaf of the matching URL Tree will be extracted. + +If the routes are defined as: + +```typescript +[ + { + path: 'my/:urlPath', + component: /* ... */, + children: [ + { + path: 'is/:matched', + component: /* ... */, + }, + ], + }, +] +``` + +Using `selectRouteParam{s}` will get the `matched` param but not the `urlPath` param, because it is not located in a leaf of the URL Tree. + +If all params in the URL Tree need to be extracted (both `urlPath` and `matched`), the following custom selector can be used. It accumulates params of all the segments in the matched route: + + + +```ts +import { Params } from '@angular/router'; +import { createSelector } from '@ngrx/store'; + +export const selectRouteNestedParams = createSelector(selectRouter, (router) => { + let currentRoute = router?.state?.root; + let params: Params = {}; + while (currentRoute?.firstChild) { + currentRoute = currentRoute.firstChild; + params = { + ...params, + ...currentRoute.params, + }; + } + return params; +}); + +export const selectRouteNestedParam = (param: string) => + createSelector(selectRouteNestedParams, (params) => params && params[param]); +``` + + + + + +Beware of using this accumulation technique when two params with the same name exist in the route (e.g. `my/:route/:id/with/another/:id`). Only the rightmost value is accessible because leftmost values are overwritten by the rightmost one in the traversal. + + + + +# Schematics Action + +--- + +## Overview + +Generates an action file that includes a sample action, +defined using the `createActionGroup` function. + +## Command + +```sh +ng generate action ActionName [options] +``` + +##### OR + +```sh +ng generate a ActionName [options] +``` + +### Options + +Provide the project name where the action files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Specify the path to create the action file. + +- `--path` + - Type: `string` + - Format: `path` + - Visible: `false` + - Default: `working directory` + +Nest the actions file within a folder based on the action `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the action file within an `actions` folder. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Specifies if api success and failure actions should be generated. + +- `--api` + - Alias: `-a` + - Type: `boolean` + - Default: `false` + +Specify the prefix for the actions. + +- `--prefix` + - Type: `string` + - Default: `load` + +## Examples + +Generate a `User` actions file with an associated spec file. + +```sh +ng generate action User +``` + +Generate a `User` actions file within a nested folder + +```sh +ng generate action User --flat false +``` + +Generate a `User` actions file within a nested `actions` folder + +```sh +ng generate action User --group +``` + + +# Schematics Container + +--- + +## Overview + +Generates a component with `Store` injected into its constructor. You can optionally provide the path to your reducers and your state interface. + +## Command + +```sh +ng generate container ComponentName [options] +``` + +##### OR + +```sh +ng generate co ComponentName [options] +``` + +### General Options + +`Angular CLI` [component options](https://github.com/angular/angular-cli/wiki/generate-component#options). + +## Container Options + +Provide the path to your file with an exported state interface + +- `--state` + - Type: `string` + +Provide the name of the interface exported for your state interface + +- `--state-interface` + - Type: `string` + - Default: `State` + +Specifies whether to create a unit test or an integration test + +- `--test-depth` + - Type: `string` + - Values: `unit|integration` + - Default: `integration` + +## Examples + +Generate a `UsersPage` container component with your reducers imported and the `Store` typed a custom interface named `MyState`. + +```sh +ng generate container UsersPage --state reducers/index.ts --state-interface MyState +``` + +If you want to generate a container with an scss file, add `@ngrx/schematics:container` to the `schematics` in your `angular.json`. + +```json +"schematics": { + "@ngrx/schematics:container": { + "style": "scss" + } +} +``` + + +# Schematics Data + +--- + +## Overview + +Generates the data entity model and service. + +## Command + +```sh +ng generate data EntityName [options] +``` + +##### OR + +```sh +ng generate dt EntityName [options] +``` + +### Options + +Provide the project name where the entity files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the data entity files within a folder based on the `data`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the data entity files within an `data` folder. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Generate a spec file alongside the data entity files. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +#### Examples + +Generate a `User` data entity files with an associated spec file. + +```sh +ng generate data User +``` + +Generate a `User` data entity files within a nested folder + +```sh +ng generate data User --flat false +``` + +Generate a `User` data entity file within a nested `data` folder + +```sh +ng generate data User --group +``` + + +# Schematics Effect + +--- + +## Overview + +Generates an effect file for `@ngrx/effects`. + +## Command + +```sh +ng generate effect EffectName [options] +``` + +##### OR + +```sh +ng generate ef EffectName [options] +``` + +## Options + +Provide the project name where the effect files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the effects file within a folder based by the effect `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the effect file within an `effects` folder. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Provide the path to a file containing an `Angular Module` and the effect will be added to its `imports` array. If the `--root` option is not included, the effect will be registered using `EffectsModule.forFeature`. + +- `--module` + - Alias: `-m` + - Type: `string` + +When used with the `--module` option, it registers an effect within the `Angular Module` using `EffectsModule.forRoot`. + +- `--root` + - Type: `boolean` + - Default: `false` + +Only provide minimal setup for the root effects setup. Only registers `EffectsModule.forRoot()` in the provided module with an empty array. + +- `--minimal` + - Type: `boolean` + - Default: `false` + +Specifies if effect has api success and failure actions wired up. + +- `--api` + - Alias: `-a` + - Type: `boolean` + - Default: `false` + +Generate a spec file alongside the effect file. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +## Examples + +Generate a `UserEffects` file and register it within the root Angular module in the root-level effects. + +```sh +ng generate effect User --root -m app.module.ts +``` + +Generate a `UserEffects` file within a `user` folder and register it with the `user.module.ts` file in the same folder. + +```sh +ng generate module User --flat false +ng generate effect user/User -m user.module.ts +``` + +Generate a `UserEffects` file within a nested `effects` folder + +```sh +ng generate effect User --group +``` + + +# Schematics Entity + +--- + +## Overview + +Generates a set of entity files for managing a collection using `@ngrx/entity` including a set of predefined `actions`, a collection `model` and a `reducer` with state selectors. + +## Command + +```sh +ng generate entity EntityName [options] +``` + +##### OR + +```sh +ng generate en EntityName [options] +``` + +## Options + +Provide the project name where the entity files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the effects file within a folder based on the entity `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Provide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`. + +- `--module` + - Alias: `-m` + - Type: `string` + +Provide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated entity interface will be imported and added to the first defined interface within the file. The entity reducer will be imported and added to the first defined object with an `ActionReducerMap` type. + +- `--reducers` + - Alias: `-r` + - Type: `string` + +Generate spec files associated with the entity files. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +## Examples + +Generate a set of `User` entity files and add it to a defined map of reducers generated from a [feature state](guide/schematics/store#examples). + +```sh +ng generate entity User --reducers reducers/index.ts +``` + +Generate a set of `User` entity files within a nested folder. + +```sh +ng generate entity User --flat false +``` + +Generate a set of `User` entity files and register it within the `Angular Module` in `app.module.ts` as a feature state. + +```sh +ng generate entity User -m app.module.ts +``` + + +# Schematics Feature + +--- + +## Overview + +Generates a feature set containing an `actions`, `effects`, `reducer`, and `selectors` file. You use this to build out a new feature area that provides a new piece of state. + +## Command + +```sh +ng generate feature FeatureName [options] +``` + +##### OR + +```sh +ng generate f FeatureName [options] +``` + +## Options + +Provide the project name where the feature files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the feature files within a folder based on the feature `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the feature files within their respective folders. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Provide the path to a file containing an `Angular Module` and the feature reducer will be added to its `imports` array using `StoreModule.forFeature`. + +- `--module` + - Alias: `-m` + - Type: `string` + +Provide the path to a `reducers` file containing a state interface and a object map of action reducers. The generated feature interface will be imported added to the first defined interface within the file. The feature reducer will be imported and added to the first defined object with an `ActionReducerMap` type. + +- `--reducers` + - Alias: `-r` + - Type: `string` + +Specifies if api success and failure `actions`, `reducer`, and `effects` should be generated as part of this feature. + +- `--api` + - Alias: `-a` + - Type: `boolean` + - Default: `false` + +Generate spec files associated with the feature files. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +## Examples + +Generate a `User` feature set and register it within an `Angular Module`. + +```sh +ng generate feature User -m app.module.ts +``` + +Generate a `User` feature set and add it to a defined set of reducers. + +```sh +ng generate feature User --group --reducers reducers/index.ts +``` + +Generate a `User` feature set within a `user` folder and register it with the `user.module.ts` file in the same `user` folder. + +```sh +ng generate module User --flat false +ng generate feature user/User -m user.module.ts --group +``` + +Generate a `User` feature set with `actions`, `effects`, `reducer`, and `selectors` file nested within their respective folders. + +```sh +ng generate feature User --group +``` + + +# @ngrx/schematics + +Scaffolding library for Angular applications using NgRx libraries. + +Schematics provides Angular CLI commands for generating files when building new NgRx feature areas and expanding existing ones. Built on top of [`Schematics`](https://angular.dev/tools/cli/schematics), this tool integrates with the [`Angular CLI`](https://angular.dev/tools/cli). + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/schematics/install) page. + +## Dependencies + +After installing `@ngrx/schematics`, install the NgRx dependencies. + +```sh +npm install @ngrx/{store,effects,entity,store-devtools} --save +``` + +```sh +yarn add @ngrx/{store,effects,entity,store-devtools} +``` + +## Initial State Setup + +Generate the initial state management and register it within the `app.module.ts` + +```sh +ng generate @ngrx/schematics:store State --root --module app.module.ts +``` + + + +The @ngrx/schematics command prefix is only needed when the default collection isn't set. + + + +## Initial Effects Setup + +Generate the root effects and register it within the `app.module.ts` + +```sh +ng generate @ngrx/schematics:effect App --root --module app.module.ts +``` + +## Adding NgRx schematics to schematicCollections + +To use `@ngrx/schematics` in your Angular CLI project, add it manually to your `angular.json` or with the following command: + +```sh +ng config cli.schematicCollections "[\"@ngrx/schematics\"]" +``` + +You should end up with the following result in your `angular.json`: + +```json +{ + "cli": { + "schematicCollections": ["@ngrx/schematics"] + } +} +``` + +Or, when the Angular schematic is also registered you should end up with following result: + +```json +{ + "cli": { + "schematicCollections": [ + "@schematics/angular", + "@ngrx/schematics" + ] + } +} +``` + +The [collection schema](https://github.com/ngrx/platform/tree/main/modules/schematics/collection.json) also has aliases to the most common schematics used to generate files. + + +# Schematics Installation + +## Installing with `ng add` + +You can make `@ngrx/schematics` the default collection for your application with the following `ng add` command (details here): + +```sh +ng add @ngrx/schematics@latest +``` + +This command will automate the following steps: + +1. Add `@ngrx/schematics` to `angular.json` > `cli > schematicCollections`. If `schematicCollections` does not exist, it will be created. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/schematics --save-dev +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/schematics --dev +``` + + +# Schematics Reducer + +--- + +## Overview + +Generates a reducer file that contains a state interface, +an initial state object for the reducer, and a reducer function. + +## Command + +```sh +ng generate reducer ReducerName [options] +``` + +##### OR + +```sh +ng generate r ReducerName [options] +``` + +### Options + +Provide the project name where the reducer files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the reducer file within a folder based on the reducer `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the reducer file within a `reducers` folder. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Provide the path to a file containing an `Angular Module` and the entity reducer will be added to its `imports` array using `StoreModule.forFeature`. + +- `--module` + - Alias: `-m` + - Type: `string` + +Provide the path to a `reducers` file containing a state interface and an object map of action reducers. The generated reducer interface will be imported and added to the first defined interface within the file. The reducer will be imported and added to the first defined object with an `ActionReducerMap` type. + +- `--reducers` + - Alias: `-r` + - Type: `string` + +Specifies if api success and failure actions should be added to the reducer. + +- `--api` + - Alias: `-a` + - Type: `boolean` + - Default: `false` + +Generate a spec file alongside the reducer file. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +## Examples + +Generate a `User` reducer file add it to a defined map of reducers generated from a [feature state](guide/schematics/store#examples). + +```sh +ng generate reducer User --reducers reducers/index.ts +``` + +Generate a `User` reducer file within a nested folder based on the reducer name. + +```sh +ng generate reducer User --flat false +``` + +Generate a `User` reducer and register it within the `Angular Module` in `app.module.ts`. + +```sh +ng generate reducer User --module app.module.ts +``` + +Generate a `User` reducer file within a nested `reducers` folder. + +```sh +ng generate reducer User --group +``` + + +# Schematics Selector + +--- + +## Overview + +Generates a selector file for `@ngrx/store`. + +## Command + +```sh +ng generate selector selectorName [options] +``` + +##### OR + +```sh +ng generate se selectorName [options] +``` + +## Options + +Provide the project name where the selector files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Nest the effects file within a folder based by the selector `name`. + +- `--flat` + - Type: `boolean` + - Default: `true` + +Group the selector file within an `selectors` folder. + +- `--group` + - Alias: `-g` + - Type: `boolean` + - Default: `false` + +Generate a spec file alongside the selector file. + +- `--skip-tests` + - Type: `boolean` + - Default: `false` + +## Examples + +Generate a selector file. + +```sh +ng generate selector User +``` + +Generate a selector file within a nested `selectors` folder + +```sh +ng generate selector User --group +``` + + +# Schematics Store + +--- + +## Overview + +Generates the initial setup for state management and registering new feature states. It registers the `@ngrx/store-devtools` integration and generates a state management file containing the state interface, the object map of action reducers and any associated meta-reducers. + +## Command + +```sh +ng generate store State [options] +``` + +##### OR + +```sh +ng generate st State [options] +``` + +## Options + +Provide the project name where the state files will be created. + +- `--project` + - Alias: `-p` + - Type: `string` + +Provide the path to a file containing an `Angular Module` and the feature state will be added to its `imports` array using `StoreModule.forFeature` or `StoreModule.forRoot`. + +- `--module` + - Alias: `-m` + - Type: `string` + +When used with the `--module` option, it registers the state within the `Angular Module` using `StoreModule.forRoot`. The `--root` option should only be used to setup the global `@ngrx/store` providers. + +- `--root` + - Type: `boolean` + - Default: `false` + +Only provide minimal setup for the root state management. Only registers `StoreModule.forRoot()` in the provided module with an empty object, and default runtime checks. + +- `--minimal` + - Type: `boolean` + - Default: `false` + +Provide the folder where the state files will be created. + +- `--state-path` + - Type: `string` + - Default: `reducers` + +Provide the name of the interface exported for your state. When defining with the `--root` option, the name of the store will be used to define the interface name. + +- `--state-interface` + - Type: `string` + - Default: `State` + +## Examples + +Generate the initial state management files and register it within the `app.module.ts`. + +```sh +ng generate store State --root --module app.module.ts +``` + +Generate an `Admin` feature state within the `admin` folder and register it with the `admin.module.ts` in the same folder. + +```sh +ng generate module admin --flat false +ng generate store admin/Admin -m admin.module.ts +``` + +Generate the initial state management files within a `store` folder and register it within the `app.module.ts`. + +```sh +ng generate store State --root --state-path store --module app.module.ts +``` + + +# DeepComputed + +The `deepComputed` function creates a `DeepSignal` when a computation result is an object literal. +It can be used as a regular computed signal, but it also contains computed signals for each nested property. + +```ts +import { signal } from '@angular/core'; +import { deepComputed } from '@ngrx/signals'; + +const limit = signal(25); +const offset = signal(0); +const totalItems = signal(100); + +const pagination = deepComputed(() => ({ + currentPage: Math.floor(offset() / limit()) + 1, + pageSize: limit(), + totalPages: Math.ceil(totalItems() / limit()), +})); + +console.log(pagination()); // logs: { currentPage: 1, pageSize: 25, totalPages: 4 } +console.log(pagination.currentPage()); // logs: 1 +console.log(pagination.pageSize()); // logs: 25 +console.log(pagination.totalPages()); // logs: 4 +``` + + + +For enhanced performance, deeply nested signals are generated lazily and initialized only upon first access. + + + + +# Signals FAQ + + + + +# @ngrx/signals + +NgRx Signals is a standalone library that provides a reactive state management solution and a set of utilities for Angular Signals. + +## Key Principles + +- **Simple and Intuitive:** Designed with ease of use in mind, NgRx Signals provides a straightforward and intuitive API for developers to efficiently work with Angular Signals. +- **Lightweight and Performant:** Keep your application size optimal with a lightweight library that adds minimal overhead to your projects and performs efficiently. +- **Declarative:** NgRx Signals is built around the concept of declarative programming, ensuring clean and concise code. +- **Modular, Extensible, and Scalable:** Modularity and extensibility are the guiding principles of this library. NgRx Signals enables the creation of independent building blocks that can be easily combined for flexible and scalable implementations. +- **Opinionated, but Flexible:** Strike a balance between flexibility and opinionation, offering customization where needed while providing thoughtful conventions for a smooth development experience. +- **Type-safe:** NgRx Signals is designed with a strong focus on type safety, ensuring the prevention of errors and misuse at compile time. + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/signals/install) page. + +## Main Features + +- [SignalStore](guide/signals/signal-store): A fully-featured state management solution that provides native support for Angular Signals and offers a robust way to manage application state. +- [SignalState](guide/signals/signal-state): A lightweight utility for managing signal-based state in Angular components and services in a concise and minimalistic manner. +- [RxJS Integration](guide/signals/rxjs-integration): A plugin for opt-in integration with RxJS, enabling easier handling of asynchronous side effects. +- [Entity Management](guide/signals/signal-store/entity-management): A plugin for manipulating and querying entity collections in a simple and performant way. + + +# Signals Installation + +## Installing with `ng add` + +You can install the `@ngrx/signals` to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/signals@latest +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/signals`. +2. Run the package manager to install the added dependency. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/signals --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/signals +``` + + +# RxJS Integration + +RxJS is still a major part of NgRx and the Angular ecosystem, and the `@ngrx/signals` package provides **opt-in** integration with RxJS APIs through the `rxjs-interop` plugin. + +## RxMethod + +The `rxMethod` is a standalone factory function designed for managing side effects by utilizing RxJS APIs. +It takes a chain of RxJS operators as input and returns a reactive method. +The reactive method can accept a static value, signal, or observable as an input argument. +Input can be typed by providing a generic argument to the `rxMethod` function. + +```ts +import { Component } from '@angular/core'; +import { map, pipe, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent { + // πŸ‘‡ This reactive method will have an input argument + // of type `number | Signal | Observable`. + readonly logDoubledNumber = rxMethod( + // πŸ‘‡ RxJS operators are chained together using the `pipe` function. + pipe( + map((num) => num * 2), + tap(console.log) + ) + ); +} +``` + +Each invocation of the reactive method pushes the input value through the reactive chain. +When called with a static value, the reactive chain executes once. + +```ts +import { Component, OnInit } from '@angular/core'; +import { map, pipe, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logDoubledNumber = rxMethod( + pipe( + map((num) => num * 2), + tap(console.log) + ) + ); + + ngOnInit(): void { + this.logDoubledNumber(1); + // console output: 2 + + this.logDoubledNumber(2); + // console output: 4 + } +} +``` + +When a reactive method is called with a signal, the reactive chain is executed every time the signal value changes. + +```ts +import { Component, OnInit, signal } from '@angular/core'; +import { map, pipe, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logDoubledNumber = rxMethod( + pipe( + map((num) => num * 2), + tap(console.log) + ) + ); + + ngOnInit(): void { + const num = signal(10); + this.logDoubledNumber(num); + // console output: 20 + + num.set(20); + // console output: 40 + } +} +``` + +When a reactive method is called with an observable, the reactive chain is executed every time the observable emits a new value. + +```ts +import { Component, OnInit } from '@angular/core'; +import { interval, map, of, pipe, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logDoubledNumber = rxMethod( + pipe( + map((num) => num * 2), + tap(console.log) + ) + ); + + ngOnInit(): void { + const num1$ = of(100, 200, 300); + this.logDoubledNumber(num1$); + // console output: 200, 400, 600 + + const num2$ = interval(2_000); + this.logDoubledNumber(num2$); + // console output: 0, 2, 4, 6, 8, 10, ... (every 2 seconds) + } +} +``` + +By default, the `rxMethod` needs to be executed within an injection context. +It's tied to its lifecycle and is automatically cleaned up when the injector is destroyed. + +### Handling API Calls + +The `rxMethod` is a great choice for handling API calls in a reactive manner. +The subsequent example demonstrates how to use `rxMethod` to fetch the book by id whenever the `selectedBookId` signal value changes. + +```ts +import { Component, inject, OnInit, signal } from '@angular/core'; +import { concatMap, filter, pipe } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { tapResponse } from '@ngrx/operators'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +@Component({ + /* ... */ +}) +export class BooksComponent implements OnInit { + readonly #booksService = inject(BooksService); + + readonly bookMap = signal>({}); + readonly selectedBookId = signal(null); + + readonly loadBookById = rxMethod( + pipe( + filter((id) => !!id && !this.bookMap()[id]), + concatMap((id) => { + return this.#booksService.getById(id).pipe( + tapResponse({ + next: (book) => this.addBook(book), + error: console.error, + }) + ); + }) + ) + ); + + ngOnInit(): void { + // πŸ‘‡ Load book by id whenever the `selectedBookId` value changes. + this.loadBookById(this.selectedBookId); + } + + addBook(book: Book): void { + this.bookMap.update((bookMap) => ({ + ...bookMap, + [book.id]: book, + })); + } +} +``` + + + +For safe handling of API responses, it is recommended to use the `tapResponse` operator from the `@ngrx/operators` package. +Learn more about it in the [tapResponse](guide/operators/operators#tapresponse) guide. + + + +The `rxMethod` function can also be utilized to define reactive methods for SignalStore. +Further details can be found in the [Reactive Store Methods](guide/signals/signal-store#reactive-store-methods) guide. + +### Reactive Methods without Arguments + +To create a reactive method without arguments, the `void` type should be specified as a generic argument to the `rxMethod` function. + +```ts +import { Component, inject, OnInit, signal } from '@angular/core'; +import { exhaustMap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { tapResponse } from '@ngrx/operators'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +@Component({ + /* ... */ +}) +export class BooksComponent implements OnInit { + readonly #booksService = inject(BooksService); + readonly books = signal([]); + + // πŸ‘‡ Creating a reactive method without arguments. + readonly loadAllBooks = rxMethod( + exhaustMap(() => { + return this.#booksService.getAll().pipe( + tapResponse({ + next: (books) => this.books.set(books), + error: console.error, + }) + ); + }) + ); + + ngOnInit(): void { + this.loadAllBooks(); + } +} +``` + +### Reactive Methods and Injector Hierarchies + +The cleanup behavior of reactive methods differs when they're created and called across different injector hierarchies. + +If the reactive method is called within the descendant injection context, the call will be automatically cleaned up when the descendant injector is destroyed. +However, when the call is made outside of the descendant injection context, it's necessary to explicitly provide the descendant injector reference to ensure proper cleanup. Otherwise, the cleanup occurs when the ascendant injector where the reactive method is initialized gets destroyed. + +```ts +import { + Component, + inject, + Injectable, + Injector, + OnInit, +} from '@angular/core'; +import { tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Injectable({ providedIn: 'root' }) +export class NumbersService { + readonly log = rxMethod(tap(console.log)); +} + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly #injector = inject(Injector); + readonly #numbersService = inject(NumbersService); + + constructor() { + const num1$ = interval(1_000); + // πŸ‘‡ Automatic cleanup when component is destroyed. + this.#numbersService.log(num1$); + } + + ngOnInit(): void { + const num2$ = interval(2_000); + // πŸ‘‡ Requires injector for cleanup when component is destroyed. + this.#numbersService.log(num2$, { injector: this.#injector }); + } +} +``` + + + +If the injector is not provided when calling the reactive method with a signal or observable outside the injection context, a warning message about a potential memory leak is displayed in development mode. + + + +### Manual Cleanup + +If a reactive method needs to be cleaned up before the injector is destroyed, manual cleanup can be performed by calling the `destroy` method. + +```ts +import { Component, OnInit } from '@angular/core'; +import { interval, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logNumber = rxMethod(tap(console.log)); + + ngOnInit(): void { + const num1$ = interval(500); + const num2$ = interval(1_000); + + this.logNumber(num1$); + this.logNumber(num2$); + + setTimeout(() => { + // πŸ‘‡ Destroy the reactive method after 3 seconds. + this.logNumber.destroy(); + }, 3_000); + } +} +``` + +When invoked, the reactive method returns the object with the `destroy` method. +This allows manual cleanup of a specific call, preserving the activity of other reactive method calls until the corresponding injector is destroyed. + +```ts +import { Component, OnInit } from '@angular/core'; +import { interval, tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logNumber = rxMethod(tap(console.log)); + + ngOnInit(): void { + const num1$ = interval(500); + const num2$ = interval(1_000); + + const num1Ref = this.logNumber(num1$); + const num2Ref = this.logNumber(num2$); + + setTimeout(() => { + // πŸ‘‡ Destroy the first reactive method call after 2 seconds. + num1Ref.destroy(); + }, 2_000); + } +} +``` + +### Initialization Outside of Injection Context + +Initialization of the reactive method outside an injection context is possible by providing an injector as the second argument to the `rxMethod` function. + +```ts +import { Component, inject, Injector, OnInit } from '@angular/core'; +import { tap } from 'rxjs'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly #injector = inject(Injector); + + ngOnInit(): void { + const logNumber = rxMethod(tap(console.log), { + injector: this.#injector, + }); + + logNumber(10); + } +} +``` + + +# SignalMethod + +`signalMethod` is a standalone factory function used for managing side effects with Angular signals. It accepts a callback and returns a processor function that can handle either a static value or a signal. The input type can be specified using a generic type argument: + +```ts +import { Component } from '@angular/core'; +import { signalMethod } from '@ngrx/signals'; + +@Component({ + /* ... */ +}) +export class NumbersComponent { + // πŸ‘‡ This method will have an input argument + // of type `number | Signal`. + readonly logDoubledNumber = signalMethod((num) => { + const double = num * 2; + console.log(double); + }); +} +``` + +`logDoubledNumber` can be called with a static value of type `number`, or a Signal of type `number`: + +```ts +@Component({ + /* ... */ +}) +export class NumbersComponent { + readonly logDoubledNumber = signalMethod((num) => { + const double = num * 2; + console.log(double); + }); + + constructor() { + this.logDoubledNumber(1); + // console output: 2 + + const num = signal(2); + this.logDoubledNumber(num); + // console output: 4 + + setTimeout(() => num.set(3), 3_000); + // console output after 3 seconds: 6 + } +} +``` + +## Automatic Cleanup + +`signalMethod` uses an `effect` internally to track the Signal changes. +By default, the `effect` runs in the injection context of the caller. In the example above, that is `NumbersComponent`. That means, that the `effect` is automatically cleaned up when the component is destroyed. + +If the call happens outside an injection context, then the injector of the `signalMethod` is used. This would be the case, if `logDoubledNumber` runs in `ngOnInit`: + +```ts +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly logDoubledNumber = signalMethod((num) => { + const double = num * 2; + console.log(double); + }); + + ngOnInit(): void { + const value = signal(2); + // πŸ‘‡ Uses the injection context of the `NumbersComponent`. + this.logDoubledNumber(value); + } +} +``` + +Even though `logDoubledNumber` is called outside an injection context, automatic cleanup occurs when `NumbersComponent` is destroyed, since `logDoubledNumber` was created within the component's injection context. + +However, when creating a `signalMethod` in an ancestor injection context, the cleanup behavior is different: + +```ts +@Injectable({ providedIn: 'root' }) +export class NumbersService { + readonly logDoubledNumber = signalMethod((num) => { + const double = num * 2; + console.log(double); + }); +} + +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly numbersService = inject(NumbersService); + + ngOnInit(): void { + const value = signal(2); + // πŸ‘‡ Uses the injection context of the `NumbersService`, which is root. + this.numbersService.logDoubledNumber(value); + } +} +``` + +Here, the `effect` outlives the component, which would produce a memory leak. + + + +If an injector is not provided when a method generated by `signalMethod` is called with a signal outside the injection context, a warning message about a potential memory leak is displayed in development mode. + + + +## Manual Cleanup + +When a `signalMethod` is created in an ancestor injection context, it's necessary to explicitly provide the caller injector to ensure proper cleanup: + +```ts +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly numbersService = inject(NumbersService); + readonly injector = inject(Injector); + + ngOnInit(): void { + const value = signal(1); + // πŸ‘‡ Providing the `NumbersComponent` injector + // to ensure cleanup on component destroy. + this.numbersService.logDoubledNumber(value, { + injector: this.injector, + }); + + // πŸ‘‡ No need to provide an injector for static values. + this.numbersService.logDoubledNumber(2); + } +} +``` + +## Initialization Outside of Injection Context + +The `signalMethod` must be initialized within an injection context. To initialize it outside an injection context, it's necessary to provide an injector as the second argument: + +```ts +@Component({ + /* ... */ +}) +export class NumbersComponent implements OnInit { + readonly injector = inject(Injector); + + ngOnInit() { + const logDoubledNumber = signalMethod( + (num) => console.log(num * 2), + { injector: this.injector } + ); + } +} +``` + +## Advantages over Effect + +At first sight, `signalMethod`, might be the same as `effect`: + +```ts +@Component({ + /* ... */ +}) +export class NumbersComponent { + readonly num = signal(2); + readonly logDoubledNumberEffect = effect(() => { + console.log(this.num() * 2); + }); + readonly logDoubledNumber = signalMethod((num) => { + console.log(num * 2); + }); + + constructor() { + this.logDoubledNumber(this.num); + } +} +``` + +However, `signalMethod` offers three distinctive advantages over `effect`: + +- **Flexible Input**: The input argument can be a static value, not just a signal. Additionally, the processor function can be called multiple times with different inputs. +- **No Injection Context Required**: Unlike an `effect`, which requires an injection context or an Injector, `signalMethod`'s "processor function" can be called without an injection context. +- **Explicit Tracking**: Only the Signal of the parameter is tracked, while Signals within the "processor function" stay untracked. + +## `signalMethod` compared to `rxMethod` + +`signalMethod` is `rxMethod` without RxJS, and is therefore much smaller in terms of bundle size. + +Be aware that RxJS is superior to Signals in managing race conditions. Signals have a glitch-free effect, meaning that for multiple synchronous changes, only the last change is propagated. Additionally, they lack powerful operators like `switchMap` or `concatMap`. + + +# SignalState + +SignalState is a lightweight utility designed for managing signal-based state in a concise and minimalistic manner. +It's suitable for managing modest-sized states and can be used directly in components, services, or standalone functions. + +## Creating a SignalState + +SignalState is instantiated using the `signalState` function, which accepts an initial state as an input argument. + +```ts +import { signalState } from '@ngrx/signals'; +import { User } from './user.model'; + +type UserState = { user: User; isAdmin: boolean }; + +const userState = signalState({ + user: { firstName: 'Eric', lastName: 'Clapton' }, + isAdmin: false, +}); +``` + +The state's type must be a record/object literal. Add arrays or primitive values to properties. + +`signalState` returns an extended version of a signal that possesses all the capabilities of a read-only signal. + +```ts +import { computed, effect } from '@angular/core'; + +// πŸ‘‡ Creating computed signals. +const userStateStr = computed(() => JSON.stringify(userState())); + +// πŸ‘‡ Performing side effects. +effect(() => console.log('userState', userState())); +``` + +Additionally, the `signalState` function generates signals for each state property. + +```ts +const user = userState.user; // type: DeepSignal +const isAdmin = userState.isAdmin; // type: Signal + +console.log(user()); // logs: { firstName: 'Eric', lastName: 'Clapton' } +console.log(isAdmin()); // logs: false +``` + +When a state property holds an object as its value, the `signalState` function generates a `DeepSignal`. +It can be used as a regular read-only signal, but it also contains signals for each property of the object it refers to. + +```ts +const firstName = user.firstName; // type: Signal +const lastName = user.lastName; // type: Signal + +console.log(firstName()); // logs: 'Eric' +console.log(lastName()); // logs: 'Clapton' +``` + + + +For enhanced performance, deeply nested signals are generated lazily and initialized only upon first access. + + + +## Updating State + +The `patchState` function provides a type-safe way to perform updates on pieces of state. +It takes a SignalState or SignalStore instance as the first argument, followed by a sequence of partial states or partial state updaters as additional arguments. + +```ts +import { patchState } from '@ngrx/signals'; + +// πŸ‘‡ Providing a partial state object. +patchState(userState, { isAdmin: true }); + +// πŸ‘‡ Providing a partial state updater. +patchState(userState, (state) => ({ + user: { ...state.user, firstName: 'Jimi' }, +})); + +// πŸ‘‡ Providing a sequence of partial state objects and/or updaters. +patchState(userState, { isAdmin: false }, (state) => ({ + user: { ...state.user, lastName: 'Hendrix' }, +})); +``` + + + +Updaters passed to the `patchState` function must perform state updates in an immutable manner. + + + +### Custom State Updaters + +Instead of providing partial states or updaters directly to the `patchState` function, it's possible to create custom state updaters. + +```ts +import { PartialStateUpdater } from '@ngrx/signals'; + +function setFirstName( + firstName: string +): PartialStateUpdater<{ user: User }> { + return (state) => ({ user: { ...state.user, firstName } }); +} + +const setAdmin = () => ({ isAdmin: true }); +``` + +Custom state updaters are easy to test and can be reused across different parts of the application. + +```ts +// Before: +patchState(userState, (state) => ({ + user: { ...state.user, firstName: 'Stevie' }, + isAdmin: true, +})); + +// After: +patchState(userState, setFirstName('Stevie'), setAdmin()); +``` + +## Usage + +### Example 1: SignalState in a Component + + + +```ts +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { signalState, patchState } from '@ngrx/signals'; + +@Component({ + selector: 'ngrx-counter', + template: ` +

    Count: {{ state.count() }}

    + + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CounterComponent { + readonly state = signalState({ count: 0 }); + + increment(): void { + patchState(this.state, (state) => ({ count: state.count + 1 })); + } + + decrement(): void { + patchState(this.state, (state) => ({ count: state.count - 1 })); + } + + reset(): void { + patchState(this.state, { count: 0 }); + } +} +``` + +
    + +### Example 2: SignalState in a Service + + + + +```ts +import { inject, Injectable } from '@angular/core'; +import { exhaustMap, pipe, tap } from 'rxjs'; +import { signalState, patchState } from '@ngrx/signals'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { tapResponse } from '@ngrx/operators'; +import { BooksService } from './books.service'; +import { Book } from './book.model'; + +type BooksState = { books: Book[]; isLoading: boolean }; + +const initialState: BooksState = { + books: [], + isLoading: false, +}; + +@Injectable() +export class BooksStore { + readonly #booksService = inject(BooksService); + readonly #state = signalState(initialState); + + readonly books = this.#state.books; + readonly isLoading = this.#state.isLoading; + + readonly loadBooks = rxMethod( + pipe( + tap(() => patchState(this.#state, { isLoading: true })), + exhaustMap(() => { + return this.#booksService.getAll().pipe( + tapResponse({ + next: (books) => patchState(this.#state, { books }), + error: console.error, + finalize: () => + patchState(this.#state, { isLoading: false }), + }) + ); + }) + ) + ); +} +``` + + + + + +```ts +import { + ChangeDetectionStrategy, + Component, + inject, + OnInit, +} from '@angular/core'; +import { BooksStore } from './books.store'; + +@Component({ + selector: 'ngrx-books', + template: ` +

    Books

    + + @if (store.isLoading()) { +

    Loading...

    + } @else { +
      + @for (book of store.books(); track book.id) { +
    • {{ book.title }}
    • + } +
    + } + `, + providers: [BooksStore], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BooksComponent implements OnInit { + readonly store = inject(BooksStore); + + ngOnInit(): void { + this.store.loadBooks(); + } +} +``` + +
    +
    + + +# Custom Store Features + +Custom SignalStore features provide a robust mechanism for extending core functionality and encapsulating common patterns, facilitating reuse across multiple stores. + +## Creating a Custom Feature + +A custom feature is created using the `signalStoreFeature` function, which accepts a sequence of base or other custom features as input arguments and merges them into a single feature. + +### Example 1: Tracking Request Status + +The following example demonstrates how to create a custom feature that includes the `requestStatus` state slice along with computed signals for checking the request status. + + + +```ts +import { computed } from '@angular/core'; +import { + signalStoreFeature, + withComputed, + withState, +} from '@ngrx/signals'; + +export type RequestStatus = + | 'idle' + | 'pending' + | 'fulfilled' + | { error: string }; +export type RequestStatusState = { requestStatus: RequestStatus }; + +export function withRequestStatus() { + return signalStoreFeature( + withState({ requestStatus: 'idle' }), + withComputed(({ requestStatus }) => ({ + isPending: computed(() => requestStatus() === 'pending'), + isFulfilled: computed(() => requestStatus() === 'fulfilled'), + error: computed(() => { + const status = requestStatus(); + return typeof status === 'object' ? status.error : null; + }), + })) + ); +} +``` + + + +In addition to the state slice and computed signals, this feature also specifies a set of state updaters for modifying the request status. + + + +```ts +export function setPending(): RequestStatusState { + return { requestStatus: 'pending' }; +} + +export function setFulfilled(): RequestStatusState { + return { requestStatus: 'fulfilled' }; +} + +export function setError(error: string): RequestStatusState { + return { requestStatus: { error } }; +} +``` + + + + + +For a custom feature, it is recommended to define state updaters as standalone functions rather than feature methods. This approach enables tree-shaking, simplifies testing, and facilitates their use alongside other updaters in a single `patchState` call. + + + +The `withRequestStatus` feature and updaters can be used to add the `requestStatus` state slice, along with the `isPending`, `isFulfilled`, and `error` computed signals to the `BooksStore`, as follows: + + + +```ts +import { inject } from '@angular/core'; +import { patchState, signalStore, withMethods } from '@ngrx/signals'; +import { setAllEntities, withEntities } from '@ngrx/signals/entities'; +import { + setFulfilled, + setPending, + withRequestStatus, +} from './request-status.feature'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +export const BooksStore = signalStore( + withEntities(), + withRequestStatus(), + withMethods((store, booksService = inject(BooksService)) => ({ + async loadAll() { + patchState(store, setPending()); + + const books = await booksService.getAll(); + patchState(store, setAllEntities(books), setFulfilled()); + }, + })) +); +``` + + + +The `BooksStore` instance will contain the following properties and methods: + +- State signals from `withEntities` feature: + - `entityMap: Signal>` + - `ids: Signal` +- Computed signals from `withEntities` feature: + - `entities: Signal` +- State signals from `withRequestStatus` feature: + - `requestStatus: Signal` +- Computed signals from `withRequestStatus` feature: + - `isPending: Signal` + - `isFulfilled: Signal` + - `error: Signal` +- Methods: + - `loadAll(): Promise` + + + +In this example, the `withEntities` feature from the `entities` plugin is utilized. +For more details, refer to the [Entity Management guide](guide/signals/signal-store/entity-management). + + + +### Example 2: Logging State Changes + +The following example shows how to create a custom feature that logs SignalStore state changes to the console. + + + +```ts +import { effect } from '@angular/core'; +import { + getState, + signalStoreFeature, + withHooks, +} from '@ngrx/signals'; + +export function withLogger(name: string) { + return signalStoreFeature( + withHooks({ + onInit(store) { + effect(() => { + const state = getState(store); + console.log(`${name} state changed`, state); + }); + }, + }) + ); +} +``` + + + +The `withLogger` feature can be used in the `BooksStore` as follows: + + + +```ts +import { signalStore } from '@ngrx/signals'; +import { withEntities } from '@ngrx/signals/entities'; +import { withRequestStatus } from './request-status.feature'; +import { withLogger } from './logger.feature'; +import { Book } from './book.model'; + +export const BooksStore = signalStore( + withEntities(), + withRequestStatus(), + withLogger('books') +); +``` + + + +State changes will be logged to the console whenever the `BooksStore` state is updated. + +## Creating a Custom Feature with Input + +The `signalStoreFeature` function provides the ability to create a custom feature that requires specific state slices, properties, and/or methods to be defined in the store where it is used. +This enables the utilization of input properties within the custom feature, even if they are not explicitly defined within the feature itself. + +The expected input type should be defined as the first argument of the `signalStoreFeature` function, using the `type` helper function from the `@ngrx/signals` package. + + + +It's recommended to define loosely-coupled/independent features whenever possible. + + + +### Example 3: Managing Selected Entity + +The following example demonstrates how to create the `withSelectedEntity` feature. + + + +```ts +import { computed } from '@angular/core'; +import { + signalStoreFeature, + type, + withComputed, + withState, +} from '@ngrx/signals'; +import { EntityId, EntityState } from '@ngrx/signals/entities'; + +export type SelectedEntityState = { + selectedEntityId: EntityId | null; +}; + +export function withSelectedEntity() { + return signalStoreFeature( + { state: type>() }, + withState({ selectedEntityId: null }), + withComputed(({ entityMap, selectedEntityId }) => ({ + selectedEntity: computed(() => { + const selectedId = selectedEntityId(); + return selectedId ? entityMap()[selectedId] : null; + }), + })) + ); +} +``` + + + +The `withSelectedEntity` feature adds the `selectedEntityId` state slice and the `selectedEntity` computed signal to the store where it is used. +However, it expects state properties from the `EntityState` type to be defined in that store. +These properties can be added to the store by using the `withEntities` feature from the `entities` plugin. + + + +```ts +import { signalStore } from '@ngrx/signals'; +import { withEntities } from '@ngrx/signals/entities'; +import { withSelectedEntity } from './selected-entity.feature'; +import { Book } from './book.model'; + +export const BooksStore = signalStore( + withEntities(), + withSelectedEntity() +); +``` + + + +The `BooksStore` instance will contain the following properties: + +- State signals from `withEntities` feature: + - `entityMap: Signal>` + - `ids: Signal` +- Computed signals from `withEntities` feature: + - `entities: Signal` +- State signals from `withSelectedEntity` feature: + - `selectedEntityId: Signal` +- Computed signals from `withSelectedEntity` feature: + - `selectedEntity: Signal` + +The `@ngrx/signals` package offers high-level type safety. +Therefore, if `BooksStore` does not contain state properties from the `EntityState` type, the compilation error will occur. + + + +```ts +import { signalStore } from '@ngrx/signals'; +import { withSelectedEntity } from './selected-entity.feature'; +import { Book } from './book.model'; + +export const BooksStore = signalStore( + withState({ books: [] as Book[], isLoading: false }), + // Error: `EntityState` properties (`entityMap` and `ids`) are missing in the `BooksStore`. + withSelectedEntity() +); +``` + + + +### Example 4: Defining Properties and Methods as Input + +In addition to state, it's also possible to define expected properties and methods in the following way: + + + +```ts +import { Signal } from '@angular/core'; +import { signalStoreFeature, type, withMethods } from '@ngrx/signals'; + +export function withBaz() { + return signalStoreFeature( + { + props: type<{ foo: Signal }>(), + methods: type<{ bar(foo: number): void }>(), + }, + withMethods((store) => ({ + baz(): void { + const foo = store.foo(); + store.bar(typeof foo === 'number' ? foo : Number(foo)); + }, + })) + ); +} +``` + + + +The `withBaz` feature can only be used in a store where the property `foo` and the method `bar` are defined. + +## Known TypeScript Issues + +Combining multiple custom features with static input may cause unexpected compilation errors: + +```ts +function withZ() { + return signalStoreFeature( + { state: type<{ x: number }>() }, + withState({ z: 10 }) + ); +} + +function withW() { + return signalStoreFeature( + { state: type<{ y: number }>() }, + withState({ w: 100 }) + ); +} + +const Store = signalStore( + withState({ x: 10, y: 100 }), + withZ(), + withW() +); // ❌ compilation error +``` + +This issue arises specifically with custom features that accept input but do not define any generic parameters. +To prevent this issue, it is recommended to specify an unused generic for such custom features: + +```ts +// πŸ‘‡ +function withZ<_>() { + return signalStoreFeature( + { state: type<{ x: number }>() }, + withState({ z: 10 }) + ); +} + +// πŸ‘‡ +function withW<_>() { + return signalStoreFeature( + { state: type<{ y: number }>() }, + withState({ w: 100 }) + ); +} + +const Store = signalStore( + withState({ x: 10, y: 100 }), + withZ(), + withW() +); // βœ… works as expected +``` + +For more complicated use cases, `withFeature` offers an alternative approach. + +## Connecting a Custom Feature with the Store + +The `withFeature` function allows passing properties, methods, or signals from a SignalStore to a custom feature. + +This is an alternative to the input approach above and allows more flexibility: + + + +```ts +import { computed, Signal } from '@angular/core'; +import { + patchState, + signalStore, + signalStoreFeature, + withComputed, + withFeature, + withMethods, + withState, +} from '@ngrx/signals'; +import { withEntities } from '@ngrx/signals/entities'; + +export function withBooksFilter(books: Signal) { + return signalStoreFeature( + withState({ query: '' }), + withComputed(({ query }) => ({ + filteredBooks: computed(() => + books().filter((b) => b.name.includes(query())) + ), + })), + withMethods((store) => ({ + setQuery(query: string): void { + patchState(store, { query }); + }, + })) + ); +} + +export const BooksStore = signalStore( + withEntities(), + withFeature(({ entities }) => withBooksFilter(entities)) +); +``` + + + + +# Custom Store Properties + +The `withProps` feature can be used to add static properties, observables, dependencies, or other custom properties to a SignalStore. +It accepts a factory function that returns an object containing additional properties for the store. +The factory function receives an object containing state signals, previously defined properties, and methods as its input argument. + +## Exposing Observables + +`withProps` can be useful for exposing observables from a SignalStore, which can serve as integration points with RxJS-based APIs: + + + +```ts +import { toObservable } from '@angular/core/rxjs-interop'; +import { signalStore, withProps, withState } from '@ngrx/signals'; +import { Book } from './book.model'; + +type BooksState = { + books: Book[]; + isLoading: boolean; +}; + +export const BooksStore = signalStore( + withState({ books: [], isLoading: false }), + withProps(({ isLoading }) => ({ + isLoading$: toObservable(isLoading), + })) +); +``` + + + +## Grouping Dependencies + +Dependencies required across multiple store features can be grouped using `withProps`: + + + +```ts +import { inject } from '@angular/core'; +import { signalStore, withProps, withState } from '@ngrx/signals'; +import { Logger } from './logger'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +type BooksState = { + books: Book[]; + isLoading: boolean; +}; + +export const BooksStore = signalStore( + withState({ books: [], isLoading: false }), + withProps(() => ({ + booksService: inject(BooksService), + logger: inject(Logger), + })), + withMethods(({ booksService, logger, ...store }) => ({ + async loadBooks(): Promise { + logger.debug('Loading books...'); + patchState(store, { isLoading: true }); + + const books = await booksService.getAll(); + logger.debug('Books loaded successfully', books); + + patchState(store, { books, isLoading: false }); + }, + })), + withHooks({ + onInit({ logger }) { + logger.debug('BooksStore initialized'); + }, + }) +); +``` + + + + +# SignalStore Entity Management + +The `@ngrx/signals/entities` plugin offers a simple and efficient way to manage entity collections with NgRx SignalStore. +This plugin provides the `withEntities` feature and a set of entity updaters. + +## `withEntities` Feature + +The `withEntities` feature integrates entity state into the store. +By default, `withEntities` requires an entity to have an `id` property, which serves as a unique identifier and must be of type `EntityId` (either a `string` or a `number`). + + + +```ts +import { computed } from '@angular/core'; +import { signalStore } from '@ngrx/signals'; +import { withEntities } from '@ngrx/signals/entities'; + +type Todo = { + id: number; + text: string; + completed: boolean; +}; + +export const TodosStore = signalStore(withEntities()); +``` + + + +The `withEntities` feature adds the following signals to the `TodosStore`: + +- `ids: Signal`: An array of all entity IDs. +- `entityMap: Signal>`: A map of entities where each key is an ID. +- `entities: Signal`: An array of all entities. + +The `ids` and `entityMap` are state slices, while `entities` is a computed signal. + +## Entity Updaters + +The `entities` plugin provides a set of standalone entity updaters. +These functions can be used with `patchState` to facilitate entity collection updates. + + + +```ts +import { patchState, signalStore, withMethods } from '@ngrx/signals'; +import { + addEntity, + removeEntities, + updateAllEntities, + withEntities, +} from '@ngrx/signals/entities'; + +type Todo = { + /* ... */ +}; + +export const TodosStore = signalStore( + withEntities(), + withMethods((store) => ({ + addTodo(todo: Todo): void { + patchState(store, addEntity(todo)); + }, + removeEmptyTodos(): void { + patchState( + store, + removeEntities(({ text }) => !text) + ); + }, + completeAllTodos(): void { + patchState(store, updateAllEntities({ completed: true })); + }, + })) +); +``` + + + +### `addEntity` + +Adds an entity to the collection. +If the entity collection has an entity with the same ID, it is not overridden and no error is thrown. + +```ts +patchState(store, addEntity(todo)); +``` + +### `addEntities` + +Adds multiple entities to the collection. +If the entity collection has entities with the same IDs, they are not overridden and no error is thrown. + +```ts +patchState(store, addEntities([todo1, todo2])); +``` + +### `prependEntity` + +Adds an entity to the beginning of the collection. +If the entity collection has an entity with the same ID, it is not added and no error is thrown. + +```ts +patchState(store, prependEntity(todo)); +``` + +### `prependEntities` + +Adds multiple entities to the beginning of the collection, maintaining their relative order. +If the entity collection has entities with the same IDs, they are not added and no error is thrown. + +```ts +patchState(store, prependEntities([todo1, todo2])); +``` + +### `updateEntity` + +Updates an entity in the collection by ID. Supports partial updates. No error is thrown if an entity doesn't exist. + +```ts +patchState( + store, + updateEntity({ id: 1, changes: { completed: true } }) +); + +patchState( + store, + updateEntity({ + id: 1, + changes: (todo) => ({ completed: !todo.completed }), + }) +); +``` + +### `updateEntities` + +Updates multiple entities in the collection by IDs or predicate. Supports partial updates. No error is thrown if entities don't exist. + +```ts +// update entities by IDs +patchState( + store, + updateEntities({ ids: [1, 2], changes: { completed: true } }) +); + +patchState( + store, + updateEntities({ + ids: [1, 2], + changes: (todo) => ({ completed: !todo.completed }), + }) +); + +// update entities by predicate +patchState( + store, + updateEntities({ + predicate: ({ text }) => text.endsWith('βœ…'), + changes: { text: '' }, + }) +); + +patchState( + store, + updateEntities({ + predicate: ({ text }) => text.endsWith('❓'), + changes: (todo) => ({ text: todo.text.slice(0, -1) }), + }) +); +``` + +### `updateAllEntities` + +Updates all entities in the collection. Supports partial updates. No error is thrown if entities don't exist. + +```ts +patchState(store, updateAllEntities({ text: '' })); + +patchState( + store, + updateAllEntities((todo) => ({ text: `${todo.text} ${todo.id}` })) +); +``` + +### `setEntity` + +Adds or replaces an entity in the collection. + +```ts +patchState(store, setEntity(todo)); +``` + +### `setEntities` + +Adds or replaces multiple entities in the collection. + +```ts +patchState(store, setEntities([todo1, todo2])); +``` + +### `setAllEntities` + +Replaces the current entity collection with the provided collection. + +```ts +patchState(store, setAllEntities([todo1, todo2, todo3])); +``` + +### `upsertEntity` + +Adds or updates an entity in the collection. +When updating, it does not replace the existing entity but merges it with the provided one. +Only the properties provided in the updated entity are merged with the existing entity. +Properties not present in the updated entity remain unchanged. + +```ts +patchState(store, upsertEntity(todo)); +``` + +### `upsertEntities` + +Adds or updates multiple entities in the collection. +When updating, it does not replace existing entities but merges them with the provided ones. +Only the properties provided in updated entities are merged with existing entities. +Properties not present in updated entities remain unchanged. + +```ts +patchState(store, upsertEntities([todo1, todo2])); +``` + +### `removeEntity` + +Removes an entity from the collection by ID. No error is thrown if an entity doesn't exist. + +```ts +patchState(store, removeEntity(1)); +``` + +### `removeEntities` + +Removes multiple entities from the collection by IDs or predicate. No error is thrown if entities don't exist. + +```ts +// remove entities by IDs +patchState(store, removeEntities([1, 2])); + +// remove entities by predicate +patchState( + store, + removeEntities((todo) => todo.completed) +); +``` + +### `removeAllEntities` + +Removes all entities from the collection. No error is thrown if entities don't exist. + +```ts +patchState(store, removeAllEntities()); +``` + +## Custom Entity Identifier + +If an entity doesn't have an identifier named `id`, a custom ID selector should be used. +The selector's return type should be either `string` or `number`. + +Custom ID selectors should be provided when adding, setting, or updating entities. +Therefore, all variations of the `add*`, `set*`, and `update*` functions include an optional second argument, which is a config object that allows specifying the `selectId` function. + + + +```ts + +import { patchState, signalStore, withMethods } from '@ngrx/signals'; +import { + addEntities, + removeEntity, + SelectEntityId, + setEntity, + updateAllEntities, + withEntities, +} from '@ngrx/signals/entities'; + +type Todo = { + key: number; + text: string; + completed: boolean; +}; + +const selectId: SelectEntityId todo.key; + +export const TodosStore = signalStore( + withEntities(), + withMethods((store) => ({ + addTodos(todos: Todo[]): void { + patchState(store, addEntities(todos, { selectId })); + }, + setTodo(todo: Todo): void { + patchState(store, setEntity(todo, { selectId })); + }, + completeAllTodos(): void { + patchState( + store, + updateAllEntities({ completed: true }, { selectId }) + ); + }, + removeTodo(key: number): void { + patchState(store, removeEntity(key)); + }, + })) +); + +``` + + + +The `remove*` updaters automatically select the correct identifier, so it is not necessary to provide a custom ID selector. + +## Named Entity Collections + +The `withEntities` feature allows specifying a custom prefix for entity properties by providing a collection name as an input argument. + + + +```ts +import { signalStore, type } from '@ngrx/signals'; +import { withEntities } from '@ngrx/signals/entities'; + +type Todo = { + id: number; + text: string; + completed: boolean; +}; + +export const TodosStore = signalStore( + // πŸ’‘ Entity type is specified using the `type` function. + withEntities({ entity: type(), collection: 'todo' }) +); +``` + + + +The names of the `TodosStore` properties are changed from `ids`, `entityMap`, and `entities` to `todoIds`, `todoEntityMap`, and `todoEntities`. + +All updaters that operate on named entity collections require a collection name. + + + +```ts +import { + patchState, + signalStore, + type, + withMethods, +} from '@ngrx/signals'; +import { + addEntity, + removeEntity, + withEntities, +} from '@ngrx/signals/entities'; + +type Todo = { + /* ... */ +}; + +export const TodosStore = signalStore( + withEntities({ entity: type(), collection: 'todo' }), + withMethods((store) => ({ + addTodo(todo: Todo): void { + patchState(store, addEntity(todo, { collection: 'todo' })); + }, + removeTodo(id: number): void { + patchState(store, removeEntity(id, { collection: 'todo' })); + }, + })) +); +``` + + + + + +Named entity collections allow managing multiple collections in a single store by using the `withEntities` feature multiple times. + +```ts +export const LibraryStore = signalStore( + withEntities({ entity: type(), collection: 'book' }), + withEntities({ entity: type(), collection: 'author' }), + withEntities({ entity: type(), collection: 'category' }), + withMethods((store) => ({ + addBook(book: Book): void { + patchState(store, addEntity(book, { collection: 'book' })); + }, + addAuthor(author: Author): void { + patchState(store, addEntity(author, { collection: 'author' })); + }, + addCategory(category: Category): void { + patchState( + store, + addEntity(category, { collection: 'category' }) + ); + }, + })) +); +``` + +Although it is possible to manage multiple collections in one store, in most cases, it is recommended to have dedicated stores for each entity type. + + + +## `entityConfig` + +The `entityConfig` function reduces repetitive code when defining a custom entity configuration and ensures strong typing. +It accepts a config object where the entity type is required, and the collection name and custom ID selector are optional. + + + +```ts +import { + patchState, + signalStore, + type, + withMethods, +} from '@ngrx/signals'; +import { + addEntity, + entityConfig, + removeEntity, + withEntities, +} from '@ngrx/signals/entities'; + +type Todo = { + key: number; + text: string; + completed: boolean; +}; + +const todoConfig = entityConfig({ + entity: type(), + collection: 'todo', + selectId: (todo) => todo.key, +}); + +export const TodosStore = signalStore( + withEntities(todoConfig), + withMethods((store) => ({ + addTodo(todo: Todo): void { + patchState(store, addEntity(todo, todoConfig)); + }, + removeTodo(todo: Todo): void { + patchState(store, removeEntity(todo, todoConfig)); + }, + })) +); +``` + + + +## Private Entity Collections + +Private entity collections are defined by using the `_` prefix for the collection name. + +```ts +const todoConfig = entityConfig({ + entity: type(), + // πŸ‘‡ private collection + collection: '_todo', +}); + +const TodosStore = signalStore( + withEntities(todoConfig), + withComputed(({ _todoEntities }) => ({ + // πŸ‘‡ exposing entity array publicly + todos: _todoEntities, + })) +); + +@Component({ + /* ... */ + template: ` +

    Todos

    + + `, + providers: [TodosStore], +}) +class TodosComponent { + readonly store = inject(TodosStore); +} +``` + + + +Learn more about private store members in the [Private Store Members](/guide/signals/signal-store/private-store-members) guide. + + + + + +# SignalStore + +NgRx SignalStore is a fully-featured state management solution that offers a robust way to manage application state. +With its native support for Signals, it provides the ability to define stores in a clear and declarative manner. +The simplicity and flexibility of SignalStore, coupled with its opinionated and extensible design, establish it as a versatile solution for effective state management in Angular. + +## Creating a Store + +A SignalStore is created using the `signalStore` function. This function accepts a sequence of store features. +Through the combination of store features, the SignalStore gains state, properties, and methods, allowing for a flexible and extensible store implementation. +Based on the utilized features, the `signalStore` function returns an injectable service that can be provided and injected where needed. + +The `withState` feature is used to add state slices to the SignalStore. +This feature accepts initial state as an input argument. As with `signalState`, the state's type must be a record/object literal. + + + +```ts +import { signalStore, withState } from '@ngrx/signals'; +import { Book } from './book.model'; + +type BooksState = { + books: Book[]; + isLoading: boolean; + filter: { query: string; order: 'asc' | 'desc' }; +}; + +const initialState: BooksState = { + books: [], + isLoading: false, + filter: { query: '', order: 'asc' }, +}; + +export const BooksStore = signalStore(withState(initialState)); +``` + + + +For each state slice, a corresponding signal is automatically created. +The same applies to nested state properties, with all deeply nested signals being generated lazily on demand. + +The `BooksStore` instance will contain the following properties: + +- `books: Signal` +- `isLoading: Signal` +- `filter: DeepSignal<{ query: string; order: 'asc' | 'desc' }>` +- `filter.query: Signal` +- `filter.order: Signal<'asc' | 'desc'>` + + + +The `withState` feature also has a signature that takes the initial state factory as an input argument. +The factory is executed within the injection context, allowing initial state to be obtained from a service or injection token. + +```ts +const BOOKS_STATE = new InjectionToken('BooksState', { + factory: () => initialState, +}); + +const BooksStore = signalStore(withState(() => inject(BOOKS_STATE))); +``` + + + +## Providing and Injecting the Store + +SignalStore can be provided locally and globally. +By default, a SignalStore is not registered with any injectors and must be included in a providers array at the component, route, or root level before injection. + + + +```ts +import { Component, inject } from '@angular/core'; +import { BooksStore } from './books.store'; + +@Component({ + /* ... */ + // πŸ‘‡ Providing `BooksStore` at the component level. + providers: [BooksStore], +}) +export class BooksComponent { + readonly store = inject(BooksStore); +} +``` + + + +When provided at the component level, the store is tied to the component lifecycle, making it useful for managing local/component state. +Alternatively, a SignalStore can be globally registered by setting the `providedIn` property to `root` when defining the store. + + + +```ts +import { signalStore, withState } from '@ngrx/signals'; +import { Book } from './book.model'; + +type BooksState = { + /* ... */ +}; + +const initialState: BooksState = { + /* ... */ +}; + +export const BooksStore = signalStore( + // πŸ‘‡ Providing `BooksStore` at the root level. + { providedIn: 'root' }, + withState(initialState) +); +``` + + + +When provided globally, the store is registered with the root injector and becomes accessible anywhere in the application. +This is beneficial for managing global state, as it ensures a single shared instance of the store across the entire application. + +## Reading State + +Signals generated for state slices can be utilized to access state values, as demonstrated below. + + + +```ts + +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { BooksStore } from './books.store'; + +@Component({ + imports: [JsonPipe], + template: ` +

    Books: {{ store.books() | json }}

    +

    Loading: {{ store.isLoading() }}

    + + +

    Pagination: {{ store.filter() | json }}

    + + +

    Query: {{ store.filter.query() }}

    +

    Order: {{ store.filter.order() }}

    + `, + providers: [BooksStore], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BooksComponent { + readonly store = inject(BooksStore); +} + +``` + +
    + +## Defining Store Properties + +Computed signals can be added to the store using the `withComputed` feature. +This feature accepts a factory function as an input argument, which is executed within the injection context. +The factory should return a dictionary of computed signals, utilizing previously defined state signals and properties that are accessible through its input argument. + + + +```ts +import { computed } from '@angular/core'; +import { signalStore, withComputed, withState } from '@ngrx/signals'; +import { Book } from './book.model'; + +type BooksState = { + /* ... */ +}; + +const initialState: BooksState = { + /* ... */ +}; + +export const BooksStore = signalStore( + withState(initialState), + // πŸ‘‡ Accessing previously defined state signals and properties. + withComputed(({ books, filter }) => ({ + booksCount: computed(() => books().length), + sortedBooks: computed(() => { + const direction = filter.order() === 'asc' ? 1 : -1; + + return books().toSorted( + (a, b) => direction * a.title.localeCompare(b.title) + ); + }), + })) +); +``` + + + + + +The `withProps` feature can be used to add static properties, observables, dependencies, and any other custom properties to a SignalStore. +For more details, see the [Custom Store Properties](/guide/signals/signal-store/custom-store-properties) guide. + + + +## Defining Store Methods + +Methods can be added to the store using the `withMethods` feature. +This feature takes a factory function as an input argument and returns a dictionary of methods. +Similar to `withComputed`, the `withMethods` factory is also executed within the injection context. +The store instance, including previously defined state signals, properties, and methods, is accessible through the factory input. + + + +```ts +import { computed } from '@angular/core'; +import { + patchState, + signalStore, + withComputed, + withMethods, + withState, +} from '@ngrx/signals'; +import { Book } from './book.model'; + +type BooksState = { + /* ... */ +}; + +const initialState: BooksState = { + /* ... */ +}; + +export const BooksStore = signalStore( + withState(initialState), + withComputed(/* ... */), + // πŸ‘‡ Accessing a store instance with previously defined state signals, + // properties, and methods. + withMethods((store) => ({ + updateQuery(query: string): void { + // πŸ‘‡ Updating state using the `patchState` function. + patchState(store, (state) => ({ + filter: { ...state.filter, query }, + })); + }, + updateOrder(order: 'asc' | 'desc'): void { + patchState(store, (state) => ({ + filter: { ...state.filter, order }, + })); + }, + })) +); +``` + + + + + +The state of the SignalStore is updated using the `patchState` function. +For more details on the `patchState` function, refer to the [Updating State](/guide/signals/signal-state#updating-state) guide. + + + + + +By default, SignalStore's state is protected from external modifications, ensuring a consistent and predictable data flow. +This is the recommended approach. +However, external updates to the state can be enabled by setting the `protectedState` option to `false` when creating a SignalStore. + +```ts +export const BooksStore = signalStore( + { protectedState: false }, // πŸ‘ˆ + withState(initialState) +); + +@Component({ + /* ... */ +}) +export class BooksComponent { + readonly store = inject(BooksStore); + + addBook(book: Book): void { + // ⚠️ The state of the `BooksStore` is unprotected from external modifications. + patchState(this.store, ({ books }) => ({ + books: [...books, book], + })); + } +} +``` + + + +In addition to methods for updating state, the `withMethods` feature can also be used to create methods for performing side effects. +Asynchronous side effects can be executed using Promise-based APIs, as demonstrated below. + + + +```ts +import { computed, inject } from '@angular/core'; +import { patchState, signalStore /* ... */ } from '@ngrx/signals'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +type BooksState = { + /* ... */ +}; + +const initialState: BooksState = { + /* ... */ +}; + +export const BooksStore = signalStore( + withState(initialState), + withComputed(/* ... */), + // πŸ‘‡ `BooksService` can be injected within the `withMethods` factory. + withMethods((store, booksService = inject(BooksService)) => ({ + /* ... */ + // πŸ‘‡ Defining a method to load all books. + async loadAll(): Promise { + patchState(store, { isLoading: true }); + + const books = await booksService.getAll(); + patchState(store, { books, isLoading: false }); + }, + })) +); +``` + + + +### Reactive Store Methods + +In more complex scenarios, opting for RxJS to handle asynchronous side effects is advisable. +To create a reactive SignalStore method that harnesses RxJS APIs, use the `rxMethod` function from the `rxjs-interop` plugin. + + + +```ts +import { computed, inject } from '@angular/core'; +import { + debounceTime, + distinctUntilChanged, + pipe, + switchMap, + tap, +} from 'rxjs'; +import { patchState, signalStore /* ... */ } from '@ngrx/signals'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { tapResponse } from '@ngrx/operators'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +type BooksState = { + /* ... */ +}; + +const initialState: BooksState = { + /* ... */ +}; + +export const BooksStore = signalStore( + withState(initialState), + withComputed(/* ... */), + withMethods((store, booksService = inject(BooksService)) => ({ + /* ... */ + // πŸ‘‡ Defining a method to load books by query. + loadByQuery: rxMethod( + pipe( + debounceTime(300), + distinctUntilChanged(), + tap(() => patchState(store, { isLoading: true })), + switchMap((query) => { + return booksService.getByQuery(query).pipe( + tapResponse({ + next: (books) => + patchState(store, { books, isLoading: false }), + error: (err) => { + patchState(store, { isLoading: false }); + console.error(err); + }, + }) + ); + }) + ) + ), + })) +); +``` + + + + + +To learn more about the `rxMethod` function, visit the [RxJS Integration](/guide/signals/rxjs-integration) page. + + + +## Putting It All Together + +The final `BooksStore` implementation with state, computed signals, and methods from this guide is shown below. + + + +```ts +import { computed, inject } from '@angular/core'; +import { + debounceTime, + distinctUntilChanged, + pipe, + switchMap, + tap, +} from 'rxjs'; +import { + patchState, + signalStore, + withComputed, + withMethods, + withState, +} from '@ngrx/signals'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { tapResponse } from '@ngrx/operators'; +import { Book } from './book.model'; +import { BooksService } from './books.service'; + +type BooksState = { + books: Book[]; + isLoading: boolean; + filter: { query: string; order: 'asc' | 'desc' }; +}; + +const initialState: BooksState = { + books: [], + isLoading: false, + filter: { query: '', order: 'asc' }, +}; + +export const BooksStore = signalStore( + withState(initialState), + withComputed(({ books, filter }) => ({ + booksCount: computed(() => books().length), + sortedBooks: computed(() => { + const direction = filter.order() === 'asc' ? 1 : -1; + + return books().toSorted( + (a, b) => direction * a.title.localeCompare(b.title) + ); + }), + })), + withMethods((store, booksService = inject(BooksService)) => ({ + updateQuery(query: string): void { + patchState(store, (state) => ({ + filter: { ...state.filter, query }, + })); + }, + updateOrder(order: 'asc' | 'desc'): void { + patchState(store, (state) => ({ + filter: { ...state.filter, order }, + })); + }, + loadByQuery: rxMethod( + pipe( + debounceTime(300), + distinctUntilChanged(), + tap(() => patchState(store, { isLoading: true })), + switchMap((query) => { + return booksService.getByQuery(query).pipe( + tapResponse({ + next: (books) => patchState(store, { books }), + error: console.error, + finalize: () => patchState(store, { isLoading: false }), + }) + ); + }) + ) + ), + })) +); +``` + + + +The `BooksStore` instance will contain the following properties and methods: + +- State signals: + - `books: Signal` + - `isLoading: Signal` + - `filter: DeepSignal<{ query: string; order: 'asc' | 'desc' }>` + - `filter.query: Signal` + - `filter.order: Signal<'asc' | 'desc'>` +- Computed signals: + - `booksCount: Signal` + - `sortedBooks: Signal` +- Methods: + - `updateQuery(query: string): void` + - `updateOrder(order: 'asc' | 'desc'): void` + - `loadByQuery: RxMethod` + + + +The `BooksStore` implementation can be enhanced further by utilizing the `entities` plugin and creating custom SignalStore features. +For more details, refer to the [Entity Management](guide/signals/signal-store/entity-management) and [Custom Store Features](guide/signals/signal-store/custom-store-features) guides. + + + +The `BooksComponent` can use the `BooksStore` to manage the state, as demonstrated below. + + + +```ts +import { + ChangeDetectionStrategy, + Component, + inject, + OnInit, +} from '@angular/core'; +import { BooksFilterComponent } from './books-filter.component'; +import { BookListComponent } from './book-list.component'; +import { BooksStore } from './books.store'; + +@Component({ + imports: [BooksFilterComponent, BookListComponent], + template: ` +

    Books ({{ store.booksCount() }})

    + + + + + `, + providers: [BooksStore], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BooksComponent implements OnInit { + readonly store = inject(BooksStore); + + ngOnInit(): void { + const query = this.store.filter.query; + // πŸ‘‡ Re-fetch books whenever the value of query signal changes. + this.store.loadByQuery(query); + } +} +``` + +
    + + + +In addition to component lifecycle hooks, SignalStore also offers the ability to define them at the store level. +Learn more about SignalStore lifecycle hooks [here](/guide/signals/signal-store/lifecycle-hooks). + + + + +# Signal Store Lifecycle Hooks + +The `@ngrx/signals` package provides the `withHooks` feature for incorporating lifecycle hooks into a SignalStore. +This feature enables performing additional logic when the store is initialized or destroyed. + +The `withHooks` feature has two signatures. +The first signature expects an object with `onInit` and/or `onDestroy` methods. +Both methods receive the store instance as input arguments. + + + +```ts +import { computed } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { interval } from 'rxjs'; +import { + patchState, + signalStore, + withState, + withHooks, + withMethods, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( + withState({ count: 0 }), + withMethods((store) => ({ + increment(): void { + patchState(store, (state) => ({ count: state.count + 1 })); + }, + })), + withHooks({ + onInit(store) { + // πŸ‘‡ Increment the `count` every 2 seconds. + interval(2_000) + // πŸ‘‡ Automatically unsubscribe when the store is destroyed. + .pipe(takeUntilDestroyed()) + .subscribe(() => store.increment()); + }, + onDestroy(store) { + console.log('count on destroy', store.count()); + }, + }) +); +``` + + + +The `onInit` hook is executed within the injection context, enabling the injection of dependencies or the utilization of functions that must be invoked within the injection context, such as `takeUntilDestroyed`. + +If there is a need to share code between lifecycle hooks or use injected dependencies within the `onDestroy` hook, the second signature can be utilized. +Similar to the `withMethods` and `withComputed` features, the second signature of the `withHooks` feature expects a factory function. +This function receives a store instance as an input argument, returns an object with `onInit` and/or `onDestroy` methods, and is executed within the injection context. + + + +```ts +export const CounterStore = signalStore( + /* ... */ + withHooks((store) => { + const logger = inject(Logger); + let interval = 0; + + return { + onInit() { + interval = setInterval(() => store.increment(), 2_000); + }, + onDestroy() { + logger.info('count on destroy', store.count()); + clearInterval(interval); + }, + }; + }) +); +``` + + + + +# Private Store Members + +SignalStore allows defining private members that cannot be accessed from outside the store by using the `_` prefix. +This includes root-level state slices, properties, and methods. + + + +```ts +import { computed } from '@angular/core'; +import { toObservable } from '@angular/core/rxjs-interop'; +import { +patchState, +signalStore, +withComputed, +withMethods, +withProps, +withState, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( +withState({ +count1: 0, +// πŸ‘‡ private state slice +\_count2: 0, +}), +withComputed(({ count1, \_count2 }) => ({ +// πŸ‘‡ private computed signal +\_doubleCount1: computed(() => count1() _ 2), +doubleCount2: computed(() => \_count2() _ 2), +})), +withProps(({ count2, \_doubleCount1 }) => ({ +// πŸ‘‡ private property +\_count2$: toObservable(count2), + doubleCount1$: toObservable(\_doubleCount1), +})), +withMethods((store) => ({ +increment1(): void { +patchState(store, { count1: store.count1() + 1 }); +}, +// πŸ‘‡ private method +\_increment2(): void { +patchState(store, { \_count2: store.\_count2() + 1 }); +}, +})), +); +``` + + + +````ts +import { Component, inject, OnInit } from '@angular/core'; +import { CounterStore } from './counter.store'; + +@Component({ +/_ ... _/ +providers: [CounterStore], +}) +export class CounterComponent implements OnInit { +readonly store = inject(CounterStore); + +ngOnInit(): void { +console.log(this.store.count1()); // βœ… +console.log(this.store.\_count2()); // ❌ + + console.log(this.store._doubleCount1()); // ❌ + console.log(this.store.doubleCount2()); // βœ… + + this.store._count2$.subscribe(console.log); // ❌ + this.store.doubleCount1$.subscribe(console.log); // βœ… + + this.store.increment1(); // βœ… + this.store._increment2(); // ❌ + +} +} + +```` + + +# Signal Store State Tracking + +State tracking enables the implementation of custom SignalStore features such as logging, state undo/redo, and storage synchronization. + +## Using `getState` and `effect` + +The `getState` function is used to get the current state value of the SignalStore. +When used within a reactive context, state changes are automatically tracked. + + + +```ts +import { effect } from '@angular/core'; +import { + getState, + patchState, + signalStore, + withHooks, + withMethods, + withState, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( + withState({ count: 0 }), + withMethods((store) => ({ + increment(): void { + patchState(store, { count: store.count() + 1 }); + }, + })), + withHooks({ + onInit(store) { + effect(() => { + // πŸ‘‡ The effect is re-executed on state change. + const state = getState(store); + console.log('counter state', state); + }); + + setInterval(() => store.increment(), 1_000); + }, + }) +); +``` + + + +Due to the `effect` glitch-free behavior, if the state is changed multiple times in the same tick, the effect function will be executed only once with the final state value. +While the asynchronous effect execution is beneficial for performance reasons, functionalities such as state undo/redo require tracking all SignalStore's state changes without coalescing state updates in the same tick. + +## Using `watchState` + +The `watchState` function allows for synchronous tracking of SignalStore's state changes. +It accepts a SignalStore instance as the first argument and a watcher function as the second argument. + +By default, the `watchState` function needs to be executed within an injection context. +It is tied to its lifecycle and is automatically cleaned up when the injector is destroyed. + + + +```ts +import { effect } from '@angular/core'; +import { + getState, + patchState, + signalStore, + watchState, + withHooks, + withState, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( + withState({ count: 0 }), + withMethods((store) => ({ + increment(): void { + patchState(store, { count: store.count() + 1 }); + }, + })), + withHooks({ + onInit(store) { + watchState(store, (state) => { + console.log('[watchState] counter state', state); + }); // logs: { count: 0 }, { count: 1 }, { count: 2 } + + effect(() => { + console.log('[effect] counter state', getState(store)); + }); // logs: { count: 2 } + + store.increment(); + store.increment(); + }, + }) +); +``` + + + +In the example above, the `watchState` function will execute the provided watcher 3 times: once with the initial counter state value and two times after each increment. +Conversely, the `effect` function will be executed only once with the final counter state value. + +### Manual Cleanup + +If a state watcher needs to be cleaned up before the injector is destroyed, manual cleanup can be performed by calling the `destroy` method. + + + +```ts +import { + patchState, + signalStore, + watchState, + withHooks, + witMethods, + withState, +} from '@ngrx/signals'; + +export const CounterStore = signalStore( + withState({ count: 0 }), + withMethods((store) => ({ + increment(): void { + patchState(store, { count: store.count() + 1 }); + }, + })), + withHooks({ + onInit(store) { + const { destroy } = watchState(store, console.log); + + setInterval(() => store.increment(), 1_000); + + // πŸ‘‡ Stop watching after 5 seconds. + setTimeout(() => destroy(), 5_000); + }, + }) +); +``` + + + +### Usage Outside of Injection Context + +The `watchState` function can be used outside an injection context by providing an injector as the second argument. + + + +```ts +import { Component, inject, Injector, OnInit } from '@angular/core'; +import { watchState } from '@ngrx/signals'; +import { CounterStore } from './counter.store'; + +@Component({ + /* ... */ + providers: [CounterStore], +}) +export class CounterComponent implements OnInit { + readonly #injector = inject(Injector); + readonly store = inject(CounterStore); + + ngOnInit(): void { + watchState(this.store, console.log, { + injector: this.#injector, + }); + + setInterval(() => this.store.increment(), 2_000); + } +} +``` + + + + +# Signal Store Testing + +A SignalStore is a straightforward Angular service, and the same testing techniques applied to other services also apply to SignalStore. This guide provides examples for common testing scenarios. + +One of the challenges in testing is managing asynchronous tasks and mocking dependencies. Although the examples use Jest, the same principles are applicable to other testing frameworks. + +There are two primary scenarios for testing: + +1. Testing the SignalStore itself. +2. Testing a component or service that utilizes the SignalStore. + +In the first scenario, the dependencies of the SignalStore should be mocked, while in the second scenario, the SignalStore itself needs to be mocked. + +--- + +When testing the SignalStore, interaction should occur through its public API, as any component or service would. + +A key concern in testing is maintainability. The more tests are coupled to internal implementations, the more frequently they are likely to break. Public APIs are generally more stable and less prone to change. + +For example, when testing the store in a loading state, avoid directly setting the loading property. Instead, trigger a loading method and assert against an exposed computed property or slice. This approach reduces dependency on internal implementations, such as properties set during the loading state. + +From this perspective, private properties or methods of the SignalStore should not be accessed. + +--- + +The SignalStore is a function that returns a class, allowing tests to instantiate the class and test it without using `TestBed`. + +However, in practice, `TestBed` is typically used due to its numerous advantages, such as the ability to mock dependencies and trigger the execution of effects. + +Additionally, key features of the SignalStore do not function properly if they do not run in an injection context. Examples include `rxMethod`, the use of `inject` within `withMethods()`, and `withHooks()`. + + + +**Note:** Using the `TestBed` is also the recommendation of the [Angular team](https://github.com/angular/angular/issues/54438#issuecomment-1971813177). + + + +## Testing the SignalStore + +The following example demonstrates the testing of a SignalStore: + +### Globally provided + + + +```ts +import { signalStore, withState } from '@ngrx/signals'; + +type Movie = { + id: number; + name: string; +}; + +type State = { movies: Movie[] }; + +export const MoviesStore = signalStore( + { providedIn: 'root' }, + withState({ + movies: [ + { id: 1, name: 'A New Hope' }, + { id: 2, name: 'Into Darkness' }, + { id: 3, name: 'The Lord of the Rings' }, + ], + }) +); +``` + + + +The `TestBed` instantiates the `MoviesStore`, enabling immediate testing. + + + +```ts +import { MoviesStore } from './movies-store'; +import { TestBed } from '@angular/core/testing'; + +describe('MoviesStore', () => { + it('should verify that three movies are available', () => { + const store = TestBed.inject(MoviesStore); + + expect(store.movies()).toHaveLength(3); + }); +}); +``` + + + +### Locally Provided + +This is possible due to the `MoviesStore` being provided globally. For locally provided stores, some adjustments to the test are required. + + + +```ts +export const MoviesStore = signalStore( + withState({ + movies: [ + // ... entries + ], + }) +); +``` + + + +The required addition is that the internal `TestingModule` must provide the `MoviesStore`. + + + +```ts +import { MoviesStore } from './movies.store'; + +describe('MoviesStore', () => { + it('should verify that three movies are available', () => { + TestBed.configureTestingModule({ + providers: [MoviesStore], + }); + + const store = TestBed.inject(MoviesStore); + + expect(store.movies()).toHaveLength(3); + }); +}); +``` + + + +### `unprotected` + +The `unprotected` function from the `@ngrx/signals/testing` plugin is used to update the protected state of a SignalStore for testing purposes. +This utility bypasses state encapsulation, making it possible to test state changes and their impacts. + +```ts +// counter.store.ts +const CounterStore = signalStore( + { providedIn: 'root' }, + withState({ count: 1 }), + withComputed(({ count }) => ({ + doubleCount: computed(() => count() * 2), + })) +); + +// counter.store.spec.ts +import { TestBed } from '@angular/core/testing'; +import { unprotected } from '@ngrx/signals/testing'; + +describe('CounterStore', () => { + it('recomputes doubleCount on count changes', () => { + const counterStore = TestBed.inject(CounterStore); + + patchState(unprotected(counterStore), { count: 10 }); + expect(counterStore.doubleCount()).toBe(20); + }); +}); +``` + +### `withComputed` + +Testing derived values of `withComputed` is also straightforward. + + + +```ts +export const MoviesStore = signalStore( + withState({ + movies: [ + // ... entries + ], + }), + withComputed((state) => ({ + moviesCount: computed(() => state.movies().length), + })) +); +``` + + + + + +```ts +import { MoviesStore } from './movies.store'; + +describe('MoviesStore', () => { + it('should verify that three movies are available', () => { + const store = TestBed.inject(MoviesStore); + + expect(store.moviesCount()).toBe(3); + }); +}); +``` + + + +### `withMethods`, Dependency Injection, and Asynchronous Tasks + +A loading method asynchronously retrieves movies by studio in this scenario. + + + +```ts +import { signalStore, withState } from '@ngrx/signals'; + +type State = { studio: string; movies: Movie[]; loading: boolean }; + +export const MoviesStore = signalStore( + withState({ + studio: '', + movies: [], + loading: false, + }), + withMethods((store) => { + const moviesService = store.inject(MoviesService); + + return { + async load(studio: string) { + this.patchState({ loading: true }); + const movies = await moviesService.loadMovies(studio); + this.patchState(store, { studio, movies, loading: false }); + }, + }; + }) +); +``` + + + +The `MoviesService` is mocked in the test, with the implementation returning the result as a `Promise`. + + + +```ts +describe('MoviesStore', () => { + it('should load movies of Warner Bros', fakeAsync(() => { + const moviesService = { + load: () => + Promise.resolve([ + { id: 1, name: 'Harry Potter' }, + { id: 2, name: 'The Dark Knight' }, + ]), + }; + + TestBed.configureTestingModule({ + providers: [ + { + provide: MoviesService, + useValue: moviesService, + }, + ], + }); + + const store = TestBed.inject(MoviesStore); + store.load('Warner Bros'); + expect(store.loading()).toBe(true); + + tick(); + + expect(store.moviesCount()).toBe(2); + expect(store.loading()).toBe(false); + })); +}); +``` + + + + + +**Note:** Manually mocking dependencies is not required. Libraries such as ng-mocks, @testing-library/angular, and [jest|jasmine]-auto-spies can be used for this purpose. + + + +### `rxMethod` + +The `load` method is created using `rxMethod` to accommodate a component that provides an input field for the studio and initiates loading as soon as a user types in a name. + +In this scenario, the `MovieService` returns an `Observable` instead of a `Promise`. + + + +```ts +export const MoviesStore = signalStore( + // ... code omitted + withMethods((store, moviesService = inject(MoviesService)) => ({ + load: rxMethod( + pipe( + tap(() => patchState(store, { loading: true })), + switchMap((studio) => + moviesService.load(studio).pipe( + tapResponse({ + next: (movies) => + patchState(store, { movies, loading: false }), + error: console.error, + }) + ) + ) + ) + ), + })) +); +``` + + + +Since `rxMethod` accepts a string as a parameter, the previous test remains valid. + +An additional focus in testing is ensuring proper handling of race conditions, which is why `switchMap` is used. + +The parameter's type can also be `Signal` or `Observable`, in addition to `number`. + +#### With Observables + +The goal is to test whether the `load` method properly handles the scenario where a new studio name is entered before or after the previous request has completed. + + + +```ts +describe('MoviesStore', () => { + // ... beforeEach and afterEach omitted + + const setup = () => { + const moviesService = { + load: jest.fn((studio: string) => + of([ + studio === 'Warner Bros' + ? { id: 1, name: 'Harry Potter' } + : { id: 2, name: 'Jurassic Park' }, + ]).pipe(delay(100)) + ), + }; + + TestBed.configureTestingModule({ + providers: [ + { + provide: MoviesService, + useValue: moviesService, + }, + ], + }); + + return TestBed.inject(MoviesStore); + }; + + it('should load two times', fakeAsync(() => { + const store = setup(); + + const studio$ = new Subject(); + store.load(studio$); + studio$.next('Warner Bros'); + + tick(100); + expect(store.movies()).toEqual([{ id: 1, name: 'Harry Potter' }]); + + studio$.next('Universal'); + tick(100); + expect(store.movies()).toEqual([ + { id: 2, name: 'Jurassic Park' }, + ]); + })); + + it('should cancel a running request when a new one is made', fakeAsync(() => { + const store = setup(); + + const studio$ = new Subject(); + store.load(studio$); + studio$.next('Warner Bros'); + + tick(50); + studio$.next('Universal'); + + tick(50); + expect(store.movies()).toEqual([]); + expect(store.loading()).toBe(true); + + tick(50); + expect(store.movies()).toEqual([ + { id: 2, name: 'Jurassic Park' }, + ]); + expect(store.loading()).toBe(false); + })); +}); +``` + + + +By utilizing the testing framework's function to manage time, both scenarios can be verified. + +The test also employs a setup function to prevent code duplication, a common pattern in testing and an alternative to the `beforeEach` function. In this case, each test can choose whether to use the setup function or not. + +#### With Signals + +Testing both scenarios with a `Signal` type as input is similar to testing with Observables. + +This similarity arises primarily due to the asynchronous tasks involved. + + + +```ts +describe('MoviesStore', () => { + // ... setup omitted + + it('should test two sequential loads with a Signal', fakeAsync(() => { + const store = setup(); + const studio = signal('Warner Bros'); + store.load(studio); + + tick(100); + expect(store.movies()).toEqual([{ id: 1, name: 'Harry Potter' }]); + + studio.set('Universal'); + tick(100); + expect(store.movies()).toEqual([ + { id: 2, name: 'Jurassic Park' }, + ]); + })); + + it('should cancel a running request when a new one is made via a Signal', fakeAsync(() => { + const store = setup(); + const studio = signal('Warner Bros'); + + effect(() => { + console.log(studio()); + }); + store.load(studio); + + tick(50); + + studio.set('Universal'); + tick(50); + expect(store.movies()).toEqual([]); + expect(store.loading()).toBe(true); + + tick(50); + expect(store.movies()).toEqual([ + { id: 2, name: 'Jurassic Park' }, + ]); + expect(store.loading()).toBe(false); + })); +}); +``` + + + +It is important to account for the glitch-free effect when using Signals. The `rxMethod` relies on `effect`, which may need to be triggered manually through `TestBed.flushEffects()`. + +If the mocked `MovieService` operates synchronously, the following test fails unless `TestBed.flushEffects()` is called. + + + +```ts +describe('MoviesStore', () => { + // ... beforeEach, and afterEach omitted + + it('should depend on flushEffects because of synchronous execution', () => { + const moviesService = { + load: jest.fn((studio: string) => + of([ + studio === 'Warner Bros' + ? { id: 1, name: 'Harry Potter' } + : { id: 2, name: 'Jurassic Park' }, + ]) + ), + }; + + TestBed.configureTestingModule({ + providers: [ + { + provide: MoviesService, + useValue: moviesService, + }, + ], + }); + + const store = TestBed.inject(MoviesStore); + const studio = signal('Warner Bros'); + store.load(studio); + TestBed.flushEffects(); // required + expect(store.movies()).toEqual([{ id: 1, name: 'Harry Potter' }]); + + studio.set('Universal'); + TestBed.flushEffects(); // required + expect(store.movies()).toEqual([ + { id: 2, name: 'Jurassic Park' }, + ]); + }); +}); +``` + + + +## Mocking the SignalStore + +What applies to testing the SignalStore also applies to mocking it. The SignalStore functions like any other service, meaning it can be mocked using the same tools and techniques applied to other services. + +The `MovieComponent` utilizes the `MoviesStore` to display movies: + + + +```ts +@Component({ + selector: 'app-movies', + template: ` + + +
      + @for (movie of store.movies(); track movie.id) { +

      {{ movie.id }}: {{ movie.name }}

      + } +
    + `, + imports: [FormsModule], +}) +export class MoviesComponent { + protected studio = signal(''); + protected readonly store = inject(MoviesStore); + + constructor() { + this.store.load(this.studio); + } +} +``` + +
    + +### Native Mocking + + + +```ts +it('should show movies (native Jest)', () => { + const load = jest.fn]>(); + + const moviesStore = { + movies: signal(new Array()), + loading: signal(false), + load, + }; + + TestBed.configureTestingModule({ + imports: [MoviesComponent], + providers: [ + { + provide: MoviesStore, + useValue: moviesStore, + }, + ], + }); + + const fixture = TestBed.createComponent(MoviesComponent); + fixture.autoDetectChanges(true); + + const studio = load.mock.calls[0][0]; + const input: HTMLInputElement = fixture.debugElement.query( + By.css('input') + ).nativeElement; + + expect(studio()).toBe(''); + + input.value = 'Warner Bros'; + input.dispatchEvent(new Event('input')); + expect(studio()).toBe('Warner Bros'); + + moviesStore.movies.set([ + { id: 1, name: 'Harry Potter' }, + { id: 2, name: 'The Dark Knight' }, + ]); + fixture.detectChanges(); + + const movieNames = fixture.debugElement + .queryAll(By.css('p')) + .map((el) => el.nativeElement.textContent); + expect(movieNames).toEqual([ + '1: Harry Potter', + '2: The Dark Knight', + ]); +}); +``` + + + +The test mocks only the properties and methods used by the component in the specific test. Even if a SignalStore contains additional methods, it is not necessary to mock all of them. + +### "Partial Mocking" via Spies + +Partial mocking can be used to mock only the `load` method. This approach allows computed properties to function correctly without requiring them to be mocked. + + + +```ts +it('should show movies (spy)', () => { + TestBed.configureTestingModule({ + imports: [MoviesComponent], + providers: [ + { + provide: MoviesService, + useValue: {}, + }, + ], + }); + + const moviesStore = TestBed.inject(MoviesStore); + const loadSpy = jest.spyOn(moviesStore, 'load'); + const fixture = TestBed.createComponent(MoviesComponent); + + fixture.autoDetectChanges(true); + + const studio = loadSpy.mock.calls[0][0]; + if (studio instanceof Observable || typeof studio === 'string') { + throw new Error('Expected signal'); + } + + const input: HTMLInputElement = fixture.debugElement.query( + By.css('input') + ).nativeElement; + + expect(studio()).toBe(''); + + input.value = 'Warner Bros'; + input.dispatchEvent(new Event('input')); + expect(studio()).toBe('Warner Bros'); + + patchState(moviesStore, { + movies: [ + { id: 1, name: 'Harry Potter' }, + { id: 2, name: 'The Dark Knight' }, + ], + }); + + fixture.detectChanges(); + + const movies = fixture.debugElement + .queryAll(By.css('p')) + .map((el) => el.nativeElement.textContent); + expect(movies).toEqual(['1: Harry Potter', '2: The Dark Knight']); +}); +``` + + + +This version requires the `MoviesStore` state to be unprotected. + +## Integration Tests + +Services attached to a component are often simple, and writing unit tests for them may not always be necessary, particularly when considering the returned value and maintenance costs. In such cases, it is more effective to test the services together with the component as a whole. This type of testing is commonly referred to as integration testing. + +The same applies to the SignalStore. If the SignalStore, such as the `MoviesStore`, is relatively simple, a single test can cover both the `MoviesComponent` and the `MoviesStore`. However, the `HttpClient` must still be replaced with a test double. + + + +```ts +it('should show movies with MoviesStore', async () => { + const fixture = TestBed.configureTestingModule({ + imports: [MoviesComponent], + providers: [provideHttpClient(), provideHttpClientTesting()], + }).createComponent(MoviesComponent); + + const ctrl = TestBed.inject(HttpTestingController); + + fixture.autoDetectChanges(true); + + const input: HTMLInputElement = fixture.debugElement.query( + By.css('input') + ).nativeElement; + input.value = 'Warner Bros'; + input.dispatchEvent(new Event('input')); + + ctrl + .expectOne('https://movies.com/studios?query=Warner%20Bros') + .flush([ + { id: 1, name: 'Harry Potter' }, + { id: 2, name: 'The Dark Knight' }, + ]); + await fixture.whenStable(); + + const movies = fixture.debugElement + .queryAll(By.css('p')) + .map((el) => el.nativeElement.textContent); + expect(movies).toEqual(['1: Harry Potter', '2: The Dark Knight']); + ctrl.verify(); +}); +``` + + + +This test assumes that the `MoviesService` sends a request. + +## Testing Custom Extensions + +An extension is responsible for playing a movie and tracking the duration of viewership. The extension provides `play` and `stop` methods, along with a Signal containing the movie's ID and the time spent watching it. + + + +```ts +type PlayTrackingState = { + _currentId: number; + _status: 'playing' | 'stopped'; + _startedAt: Date | undefined; + trackedData: Record; +}; + +const initialState: PlayTrackingState = { + _currentId: 0, + _status: 'stopped', + _startedAt: undefined, + trackedData: {}, +}; + +export const withPlayTracking = () => + signalStoreFeature( + withState(initialState), + withMethods((store) => { + const stop = () => { + const startedAt = store._startedAt(); + if (!startedAt || store._status() === 'stopped') { + return; + } + + const timeSpent = new Date().getTime() - startedAt.getTime(); + const alreadySpent = + store.trackedData()[store._currentId()] ?? 0; + patchState(store, (state) => ({ + _currentId: 0, + _status: 'stopped' as const, + trackedData: { + ...state.trackedData, + [state._currentId]: alreadySpent + timeSpent, + }, + })); + }; + + return { + play(id: number) { + stop(); + patchState(store, { + _currentId: id, + _status: 'playing', + _startedAt: new Date(), + }); + }, + stop, + }; + }) + ); +``` + + + +There are two options for testing this extension: in combination with the `MoviesStore` or in isolation. + +When tested with the `MoviesStore`, the same approach as in previous examples is followed. + +To test the extension in isolation, an artificial "Wrapper" SignalStore is created. The test process remains straightforward. + + + +```ts +describe('withTrackedPlay', () => { + const TrackedPlayStore = signalStore( + { providedIn: 'root' }, + withPlayTracking() + ); + + it('should track movies', fakeAsync(() => { + const store = TestBed.inject(TrackedPlayStore); + + store.play(1); + tick(1000); + + store.stop(); + store.play(2); + tick(1000); + + store.play(3); + tick(1000); + + store.play(1); + tick(1000); + store.stop(); + + expect(store.trackedData()).toEqual({ + 1: 2000, + 2: 1000, + 3: 1000, + }); + })); +}); +``` + + + + +# Action Groups + +
    +
    + +
    +
    + +The `createActionGroup` function creates a group of action creators with the same source. +It accepts an action group source and an event dictionary as input arguments, where an event is a key-value pair of an event name and event props. + + + +```ts +import { createActionGroup, emptyProps, props } from '@ngrx/store'; + +export const ProductsPageActions = createActionGroup({ + source: 'Products Page', + events: { + // defining an event without payload using the `emptyProps` function + Opened: emptyProps(), + + // defining an event with payload using the `props` function + 'Pagination Changed': props<{ page: number; offset: number }>(), + + // defining an event with payload using the props factory + 'Query Changed': (query: string) => ({ query }), + }, +}); +``` + + + + + +The `emptyProps` function is used to define an action creator without payload within an action group. + + + +If we create a new action creator using the `createAction` function by copying the previous one but accidentally forget to change its type, the compilation will pass. +Fortunately, this is not the case with the `createActionGroup` function because we will get a compilation error if two actions from the same group have the same type. + +The `createActionGroup` function returns a dictionary of action creators where the name of each action creator is created by camel-casing the event name, and the action type is created using the "[Source] Event Name" pattern. +Also, there is no longer a need for barrel files or named imports because the action group can be imported directly into another file. + + + +```ts +import { Component, inject, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import { ProductsPageActions } from './products-page.actions'; + +@Component({ + /* ... */ +}) +export class ProductsComponent implements OnInit { + private readonly store = inject(Store); + + ngOnInit(): void { + // action type: [Products Page] Opened + this.store.dispatch(ProductsPageActions.opened()); + } + + onPaginationChange(page: number, offset: number): void { + // action type: [Products Page] Pagination Changed + this.store.dispatch( + ProductsPageActions.paginationChanged({ page, offset }) + ); + } + + onQueryChange(query: string): void { + // action type: [Products Page] Query Changed + this.store.dispatch(ProductsPageActions.queryChanged(query)); + } +} +``` + + + +## Alternative way of defining event names + +In the previous example, event names are defined in the title case format. +In that case, it can be challenging to search for unused action creators because their names are automatically generated by camel-casing the event names. + +The `createActionGroup` function provides the ability to define event names in the camel case format as well, so action creators will have the same names as events. +This makes it easier to search for their usage within the codebase. + + + +```ts +import { createActionGroup, props } from '@ngrx/store'; + +import { Product } from './product.model'; + +export const ProductsApiActions = createActionGroup({ + source: 'Products API', + events: { + productsLoadedSuccess: props<{ products: Product[] }>(), + productsLoadedFailure: props<{ errorMsg: string }>(), + }, +}); + +// generated action creators: +const { + productsLoadedSuccess, // type: "[Products API] productsLoadedSuccess" + productsLoadedFailure, // type: "[Products API] productsLoadedFailure" +} = ProductsApiActions; +``` + + + +## Limitations + +An action group uses the event names to create properties within the group that represent the action creators. +The action creator names are generated and are the camelCased version of the event names. +For example, for the event name `Query Changed`, the action creator name will be `queryChanged`. +Therefore, it is not possible to define action creators whose names differ from their event names using the `createActionGroup` function. + + +# Actions + +Actions are one of the main building blocks in NgRx. Actions express _unique events_ that happen throughout your application. From user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events are described with actions. + +## Introduction + +Actions are used in many areas of NgRx. Actions are the inputs and outputs of many systems in NgRx. Actions help you to understand how events are handled in your application. This guide provides general rules and examples for writing actions in your application. + +## The Action interface + +An `Action` in NgRx is made up of a simple interface: + + + +```ts +interface Action { + type: string; +} +``` + + + +The interface has a single property, the `type`, represented as a string. The `type` property is for describing the action that will be dispatched in your application. The value of the type comes in the form of `[Source] Event` and is used to provide a context of what category of action it is, and where an action was dispatched from. You add properties to an action to provide additional context or metadata for an action. + +Listed below are examples of actions written as plain old JavaScript objects (POJOs): + +```json +{ + "type": "[Auth API] Login Success" +} +``` + +This action describes an event triggered by a successful authentication after interacting with a backend API. + +```json +{ + type: '[Login Page] Login', + username: string; + password: string; +} +``` + +This action describes an event triggered by a user clicking a login button from the login page to attempt to authenticate a user. The username and password are defined as additional metadata provided from the login page. + +## Writing actions + +There are a few rules to writing good actions within your application. + +- Upfront - write actions before developing features to understand and gain a shared knowledge of the feature being implemented. +- Divide - categorize actions based on the event source. +- Many - actions are inexpensive to write, so the more actions you write, the better you express flows in your application. +- Event-Driven - capture _events_ **not** _commands_ as you are separating the description of an event and the handling of that event. +- Descriptive - provide context that are targeted to a unique event with more detailed information you can use to aid in debugging with the developer tools. + +Following these guidelines helps you follow how these actions flow throughout your application. + +Let's look at an example action of initiating a login request. + + + +```ts +import { createAction, props } from '@ngrx/store'; + +export const login = createAction( + '[Login Page] Login', + props<{ username: string; password: string }>() +); +``` + + + +The `createAction` function returns a function, that when called returns an object in the shape of the `Action` interface. The `props` method is used to define any additional metadata needed for the handling of the action. Action creators provide a consistent, type-safe way to construct an action that is being dispatched. + +Use the action creator to return the `Action` when dispatching. + + + +```ts + onSubmit(username: string, password: string) { + store.dispatch(login({ username: username, password: password })); + } +``` + + + +The `login` action creator receives an object of `username` and `password` and returns a plain JavaScript object with a `type` property of `[Login Page] Login`, with `username` and `password` as additional properties. + +The returned action has very specific context about where the action came from and what event happened. + +- The category of the action is captured within the square brackets `[]`. +- The category is used to group actions for a particular area, whether it be a component page, backend API, or browser API. +- The `Login` text after the category is a description about what event occurred from this action. In this case, the user clicked a login button from the login page to attempt to authenticate with a username and password. + + + +**Note:** You can also write actions using class-based action creators, which was the previously defined way before action creators were introduced in NgRx. If you are looking for examples of class-based action creators, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/store/actions). + + + +## Dispatching actions on signal changes + +You can also dispatch functions that return actions, with property values derived from signals: + + + +```ts +class BookComponent { + bookId = input.required(); + + constructor(store: Store) { + store.dispatch(() => loadBook({ id: this.bookId() }))); + } +} +``` + + + +`dispatch` executes initially and every time the `bookId` changes. If `dispatch` is called within an injection context, the signal is tracked until the context is destroyed. In the example above, that would be when `BookComponent` is destroyed. + +When `dispatch` is called outside a component's injection context, the signal is tracked globally throughout the application's lifecycle. To ensure proper cleanup in such a case, provide the component's injector to the `dispatch` method: + + + +```ts +class BookComponent { + bookId = input.required(); + injector = inject(Injector); + store = inject(Store); + + ngOnInit() { + // runs outside the injection context + this.store.dispatch(() => loadBook({ id: this.bookId() }), { + injector: this.injector, + }); + } +} +``` + + + +When passing a function to the `dispatch` method, it returns an `EffectRef`. For manual cleanup, call the `destroy` method on the `EffectRef`: + + + +```ts +class BookComponent { + bookId = input.required(); + loadBookEffectRef: EffectRef | undefined; + store = inject(Store); + + ngOnInit() { + // uses the injection context of Store, i.e. root injector + this.loadBookEffectRef = this.store.dispatch(() => + loadBook({ id: this.bookId() }) + ); + } + + ngOnDestroy() { + if (this.loadBookEffectRef) { + // destroys the effect + this.loadBookEffectRef.destroy(); + } + } +} +``` + + + +## Next Steps + +Action's only responsibilities are to express unique events and intents. Learn how they are handled in the guides below. + +- [Reducers](guide/store/reducers) +- [Effects](guide/effects) + + +# Feature Creators + +
    +
    + +
    +
    + +## What is an NgRx feature? + +There are three main building blocks of global state management with `@ngrx/store`: actions, reducers, and selectors. +For a particular feature state, we create a reducer for handling state transitions based on the dispatched actions +and selectors to obtain slices of the feature state. Also, we need to define a feature name needed to register +the feature reducer in the NgRx store. Therefore, we can consider the NgRx feature as a grouping of the feature name, +feature reducer, and selectors for the particular feature state. + +## Using feature creator + +The `createFeature` function reduces repetitive code in selector files by generating a feature selector and child selectors +for each feature state property. It accepts an object containing a feature name and a feature reducer as the input argument: + + + +```ts +import { createFeature, createReducer, on } from '@ngrx/store'; +import { Book } from './book.model'; + +import * as BookListPageActions from './book-list-page.actions'; +import * as BooksApiActions from './books-api.actions'; + +interface State { + books: Book[]; + loading: boolean; +} + +const initialState: State = { + books: [], + loading: false, +}; + +export const booksFeature = createFeature({ + name: 'books', + reducer: createReducer( + initialState, + on(BookListPageActions.enter, (state) => ({ + ...state, + loading: true, + })), + on(BooksApiActions.loadBooksSuccess, (state, { books }) => ({ + ...state, + books, + loading: false, + })) + ), +}); + +export const { + name, // feature name + reducer, // feature reducer + selectBooksState, // feature selector + selectBooks, // selector for `books` property + selectLoading, // selector for `loading` property +} = booksFeature; +``` + + + +An object created with the `createFeature` function contains a feature name, a feature reducer, a feature selector, +and a selector for each feature state property. All generated selectors have the "select" prefix, and the feature selector has +the "State" suffix. In this example, the name of the feature selector is `selectBooksState`, where "books" is the feature name. +The names of the child selectors are `selectBooks` and `selectLoading`, based on the property names of the books feature state. + +The generated selectors can be used independently or to create other selectors: + + + +```ts +import { createSelector } from '@ngrx/store'; +import { booksFeature } from './books.reducer'; + +export const selectBookListPageViewModel = createSelector( + booksFeature.selectBooks, + booksFeature.selectLoading, + (books, loading) => ({ books, loading }) +); +``` + + + +## Providing Extra Selectors + +`createFeature` also can be used to provide extra selectors for the feature state with the `extraSelectors` option: + + + +```ts +import { createFeature, createReducer, on } from '@ngrx/store'; +import { Book } from './book.model'; + +import * as BookListPageActions from './book-list-page.actions'; + +interface State { + books: Book[]; + query: string; +} + +const initialState: State = { + books: [], + query: '', +}; + +export const booksFeature = createFeature({ + name: 'books', + reducer: createReducer( + initialState, + on(BookListPageActions.search, (state, action) => ({ + ...state, + query: action.query, + })) + ), + extraSelectors: ({ selectQuery, selectBooks }) => ({ + selectFilteredBooks: createSelector( + selectQuery, + selectBooks, + (query, books) => + books.filter((book) => book.title.includes(query)) + ), + }), +}); +``` + + + +The `extraSelectors` option accepts a function that takes the generated selectors as input arguments and returns an object of all the extra selectors. We can use it to define as many extra selectors as we need. + +### Reusing Extra Selectors + +Reusing extra selectors can be done by defining `extraSelectors` factory in the following way: + + + +```ts +import { createFeature, createReducer, on } from '@ngrx/store'; +import { Book } from './book.model'; + +import * as BookListPageActions from './book-list-page.actions'; + +interface State { + books: Book[]; + query: string; +} + +const initialState: State = { + books: [], + query: '', +}; + +export const booksFeature = createFeature({ + name: 'books', + reducer: createReducer( + initialState, + on(BookListPageActions.search, (state, action) => ({ + ...state, + query: action.query, + })) + ), + extraSelectors: ({ selectQuery, selectBooks }) => { + const selectFilteredBooks = createSelector( + selectQuery, + selectBooks, + (query, books) => + books.filter((book) => book.title.includes(query)) + ); + const selectFilteredBooksWithRating = createSelector( + selectFilteredBooks, + (books) => books.filter((book) => book.ratingsCount >= 1) + ); + + return { selectFilteredBooks, selectFilteredBooksWithRating }; + }, +}); +``` + + + +## Feature registration + +Registering the feature reducer in the store can be done by passing the entire feature object to the `StoreModule.forFeature` method: + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; + +import { booksFeature } from './books.reducer'; + +@NgModule({ + imports: [StoreModule.forFeature(booksFeature)], +}) +export class BooksModule {} +``` + + + +### Using the Standalone API + +Registering the feature can be done using the standalone APIs if you are bootstrapping an Angular application using standalone features. + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore, provideState } from '@ngrx/store'; + +import { AppComponent } from './app.component'; +import { booksFeature } from './books.reducer'; + +bootstrapApplication(AppComponent, { + providers: [provideStore(), provideState(booksFeature)], +}); +``` + + + +Feature states can also be registered in the `providers` array of the route config. + + + +```ts +import { Route } from '@angular/router'; +import { provideState } from '@ngrx/store'; + +import { booksFeature } from './books.reducer'; + +export const routes: Route[] = [ + { + path: 'books', + providers: [provideState(booksFeature)], + }, +]; +``` + + + +## Restrictions + +The `createFeature` function cannot be used for features whose state contains optional properties. +In other words, all state properties have to be passed to the initial state object. + +So, if the state contains optional properties: + + + +```ts +interface State { + books: Book[]; + activeBookId?: string; +} + +const initialState: State = { + books: [], +}; +``` + + + +Each optional symbol (`?`) have to be replaced with `| null` or `| undefined`: + + + +```ts +interface State { + books: Book[]; + activeBookId: string | null; + // or activeBookId: string | undefined; +} + +const initialState: State = { + books: [], + activeBookId: null, + // or activeBookId: undefined, +}; +``` + + + + +# @ngrx/store + +Store is RxJS powered global state management for Angular applications, inspired by Redux. Store is a controlled state container designed to help write performant, consistent applications on top of Angular. + +## Key concepts + +- [Actions](guide/store/actions) describe unique events that are dispatched from components and services. +- State changes are handled by pure functions called [reducers](guide/store/reducers) that take the current state and the latest action to compute a new state. +- [Selectors](guide/store/selectors) are pure functions used to select, derive and compose pieces of state. +- State is accessed with the `Store`, an observable of state and an observer of actions. + +## Local state management + +NgRx Store is mainly for managing global state across an entire application. In cases where you need to manage temporary or local component state, consider using [NgRx Signals](guide/signals). + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/store/install) page. + +## Diagram + +The following diagram represents the overall general flow of application state in NgRx. + +
    + NgRx State Management Lifecycle Diagram +
    + + + +**Note:** All `Actions` that are dispatched within an application state are always first processed by the `Reducers` before being handled by the `Effects` of the application state. + + + +## Tutorial + +The following tutorial shows you how to manage the state of a counter, and how to select and display it within an Angular component. Try the . + +1. Generate a new project using StackBlitz . + +2. Right click on the `app` folder in StackBlitz and create a new file named `counter.actions.ts` to describe the counter actions to increment, decrement, and reset its value. + + + +```ts +import { createAction } from '@ngrx/store'; + +export const increment = createAction( + '[Counter Component] Increment' +); +export const decrement = createAction( + '[Counter Component] Decrement' +); +export const reset = createAction('[Counter Component] Reset'); +``` + + + +3. Define a reducer function to handle changes in the counter value based on the provided actions. + + + +```ts +import { createReducer, on } from '@ngrx/store'; +import { increment, decrement, reset } from './counter.actions'; + +export const initialState = 0; + +export const counterReducer = createReducer( + initialState, + on(increment, (state) => state + 1), + on(decrement, (state) => state - 1), + on(reset, (state) => 0) +); +``` + + + +4. Import the `StoreModule` from `@ngrx/store` and the `counter.reducer` file. + + + +```ts +import { StoreModule } from '@ngrx/store'; +import { counterReducer } from './counter.reducer'; +``` + + + +5. Add the `StoreModule.forRoot` function in the `imports` array of your `AppModule` with an object containing the `count` and the `counterReducer` that manages the state of the counter. The `StoreModule.forRoot()` method registers the global providers needed to access the `Store` throughout your application. + + + +```ts +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; + +import { StoreModule } from '@ngrx/store'; +import { counterReducer } from './counter.reducer'; + +@NgModule({ + declarations: [AppComponent], + imports: [ + BrowserModule, + StoreModule.forRoot({ count: counterReducer }), + ], + providers: [], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + + + +6. Create a new file called `my-counter.component.ts` in a folder named `my-counter` within the `app` folder that will define a new component called `MyCounterComponent`. This component will render buttons that allow the user to change the count state. Also, create the `my-counter.component.html` file within this same folder. + + + +```ts +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-my-counter', + templateUrl: './my-counter.component.html', +}) +export class MyCounterComponent { + count$: Observable; + + constructor() { + // TODO: Connect `this.count$` stream to the current store `count` state + } + + increment() { + // TODO: Dispatch an increment action + } + + decrement() { + // TODO: Dispatch a decrement action + } + + reset() { + // TODO: Dispatch a reset action + } +} +``` + + + + + +```ts + + +
    Current Count: {{ count$ | async }}
    + + + + +``` + +
    + +7. Add the new component to your AppModule's declarations and declare it in the template: + + + +```html + +``` + + + + + +```ts +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; + +import { StoreModule } from '@ngrx/store'; +import { counterReducer } from './counter.reducer'; +import { MyCounterComponent } from './my-counter/my-counter.component'; + +@NgModule({ + declarations: [AppComponent, MyCounterComponent], + imports: [ + BrowserModule, + StoreModule.forRoot({ count: counterReducer }), + ], + providers: [], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + + + +8. Inject the store into `MyCounterComponent` and connect the `count$` stream to the store's `count` state. Implement the `increment`, `decrement`, and `reset` methods by dispatching actions to the store. + + + +```ts +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { increment, decrement, reset } from '../counter.actions'; + +@Component({ + selector: 'app-my-counter', + templateUrl: './my-counter.component.html', +}) +export class MyCounterComponent { + count$: Observable; + + constructor(private store: Store<{ count: number }>) { + this.count$ = store.select('count'); + } + + increment() { + this.store.dispatch(increment()); + } + + decrement() { + this.store.dispatch(decrement()); + } + + reset() { + this.store.dispatch(reset()); + } +} +``` + + + +And that's it! Click the increment, decrement, and reset buttons to change the state of the counter. + +Let's cover what you did: + +- Defined actions to express events. +- Defined a reducer function to manage the state of the counter. +- Registered the global state container that is available throughout your application. +- Injected the `Store` service to dispatch actions and select the current state of the counter. + +## Next Steps + +Learn about the architecture of an NgRx application through [actions](guide/store/actions), [reducers](guide/store/reducers), and [selectors](guide/store/selectors). + + +# Store Installation + +## Installing with `ng add` + +You can install the Store to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/store@latest +``` + +### Optional `ng add` flags + +| flag | description | value type | default value | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `--path` | Path to the module that you wish to add the import for the StoreModule to. | `string` | +| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `StoreModule` to. | `string` | +| `--module` | Name of file containing the module that you wish to add the import for the `StoreModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string` | `app` | +| `--minimal` | Flag to only provide minimal setup for the root state management. Only registers `StoreModule.forRoot()` in the provided `module` with an empty object, and default runtime checks. | `boolean` | `true` | +| `--statePath` | The file path to create the state in. | `string` | `reducers` | +| `--stateInterface` | The type literal of the defined interface for the state. | `string` | `State` | + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/store`. +2. Run `npm install` to install those dependencies. +3. Update your `src/app/app.module.ts` > `imports` array with `StoreModule.forRoot({})` +4. If the project is using a `standalone bootstrap`, it adds `provideStore()` into the application config. + +```sh +ng add @ngrx/store@latest --no-minimal +``` + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/store`. +2. Run `npm install` to install those dependencies. +3. Create a `src/app/reducers` folder, unless the `statePath` flag is provided, in which case this would be created based on the flag. +4. Create a `src/app/reducers/index.ts` file with an empty `State` interface, an empty `reducers` map, and an empty `metaReducers` array. This may be created under a different directory if the `statePath` flag is provided. +5. Update your `src/app/app.module.ts` > `imports` array with `StoreModule.forRoot(reducers, { metaReducers })`. If you provided flags then the command will attempt to locate and update module found by the flags. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/store --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/store +``` + + +# Store Meta-reducers + +`@ngrx/store` composes your map of reducers into a single reducer. + +Developers can think of meta-reducers as hooks into the action->reducer pipeline. Meta-reducers allow developers to pre-process actions before _normal_ reducers are invoked. + +Use the `metaReducers` configuration option to provide an array of meta-reducers that are composed from right to left. + +**Note:** Meta-reducers in NgRx are similar to middleware used in Redux. + +### Using a meta-reducer to log all actions + + + +```ts +import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store'; +import { reducers } from './reducers'; + +// console.log all actions +export function debug( + reducer: ActionReducer +): ActionReducer { + return function (state, action) { + console.log('state', state); + console.log('action', action); + + return reducer(state, action); + }; +} + +export const metaReducers: MetaReducer[] = [debug]; + +@NgModule({ + imports: [StoreModule.forRoot(reducers, { metaReducers })], +}) +export class AppModule {} +``` + + + + +# Store Reducers + +Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which [actions](guide/store/actions) to handle based on the action's type. + +## Introduction + +Reducers are pure functions in that they produce the same output for a given input. They are without side effects and handle each state transition synchronously. Each reducer function takes the latest `Action` dispatched, the current state, and determines whether to return a newly modified state or the original state. This guide shows you how to write reducer functions, register them in your `Store`, and compose feature states. + +## The reducer function + +There are a few consistent parts of every piece of state managed by a reducer. + +- An interface or type that defines the shape of the state. +- The arguments including the initial state or current state and the current action. +- The functions that handle state changes for their associated action(s). + +Below is an example of a set of actions to handle the state of a scoreboard, +and the associated reducer function. + +First, define some actions for interacting with a piece of state. + + + +```ts +import { createAction, props } from '@ngrx/store'; + +export const homeScore = createAction('[Scoreboard Page] Home Score'); +export const awayScore = createAction('[Scoreboard Page] Away Score'); +export const resetScore = createAction( + '[Scoreboard Page] Score Reset' +); +export const setScores = createAction( + '[Scoreboard Page] Set Scores', + props<{ game: Game }>() +); +``` + + + +Next, create a reducer file that imports the actions and define +a shape for the piece of state. + +### Defining the state shape + +Each reducer function is a listener of actions. The scoreboard actions defined above describe the possible transitions handled by the reducer. Import multiple sets of actions to handle additional state transitions within a reducer. + + + +```ts +import { Action, createReducer, on } from '@ngrx/store'; +import * as ScoreboardPageActions from '../actions/scoreboard-page.actions'; + +export interface State { + home: number; + away: number; +} +``` + + + +You define the shape of the state according to what you are capturing, whether it be a single type such as a number, or a more complex object with multiple properties. + +### Setting the initial state + +The initial state gives the state an initial value, or provides a value if the current state is `undefined`. You set the initial state with defaults for your required state properties. + +Create and export a variable to capture the initial state with one or +more default values. + + + +```ts +export const initialState: State = { + home: 0, + away: 0, +}; +``` + + + +The initial values for the `home` and `away` properties of the state are 0. + +### Creating the reducer function + +The reducer function's responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state of the scoreboard using the `createReducer` function. + + + +```ts +export const scoreboardReducer = createReducer( + initialState, + on(ScoreboardPageActions.homeScore, (state) => ({ + ...state, + home: state.home + 1, + })), + on(ScoreboardPageActions.awayScore, (state) => ({ + ...state, + away: state.away + 1, + })), + on(ScoreboardPageActions.resetScore, (state) => ({ + home: 0, + away: 0, + })), + on(ScoreboardPageActions.setScores, (state, { game }) => ({ + home: game.home, + away: game.away, + })) +); +``` + + + +In the example above, the reducer is handling 4 actions: `[Scoreboard Page] Home Score`, `[Scoreboard Page] Away Score`, `[Scoreboard Page] Score Reset` and `[Scoreboard Page] Set Scores`. Each action is strongly-typed. Each action handles the state transition immutably. This means that the state transitions are not modifying the original state, but are returning a new state object using the spread operator. The spread syntax copies the properties from the current state into the object, creating a new reference. This ensures that a new state is produced with each change, preserving the purity of the change. This also promotes referential integrity, guaranteeing that the old reference was discarded when a state change occurred. + + + +**Note:** The [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) only does shallow copying and does not handle deeply nested objects. You need to copy each level in the object to ensure immutability. There are libraries that handle deep copying including [lodash](https://lodash.com) and [immer](https://github.com/mweststrate/immer). + + + +When an action is dispatched, _all registered reducers_ receive the action. Whether they handle the action is determined by the `on` functions that associate one or more actions with a given state change. + + + +**Note:** You can also write reducers using switch statements, which was the previously defined way before reducer creators were introduced in NgRx. If you are looking for examples of reducers using switch statements, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/store/reducers). + + + +## Registering root state + +The state of your application is defined as one large object. Registering reducer functions to manage parts of your state only defines keys with associated values in the object. To register the global `Store` within your application, use the `StoreModule.forRoot()` method with a map of key/value pairs that define your state. The `StoreModule.forRoot()` registers the global providers for your application, including the `Store` service you inject into your components and services to dispatch actions and select pieces of state. + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { scoreboardReducer } from './reducers/scoreboard.reducer'; + +@NgModule({ + imports: [StoreModule.forRoot({ game: scoreboardReducer })], +}) +export class AppModule {} +``` + + + +Registering states with `StoreModule.forRoot()` ensures that the states are defined upon application startup. In general, you register root states that always need to be available to all areas of your application immediately. + +### Using the Standalone API + +Registering the root store and state can also be done using the standalone APIs if you are bootstrapping an Angular application using standalone features. + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore, provideState } from '@ngrx/store'; + +import { AppComponent } from './app.component'; +import { scoreboardReducer } from './reducers/scoreboard.reducer'; + +bootstrapApplication(AppComponent, { + providers: [ + provideStore(), + provideState({ name: 'game', reducer: scoreboardReducer }), + ], +}); +``` + + + + + +**Note:** Although you can register reducers in the `provideStore()` function, we recommend keeping `provideStore()` empty and using the `provideState()` function to register feature states in the root `providers` array. + + + +## Registering feature state + +Feature states behave in the same way root states do, but allow you to define them with specific feature areas in your application. Your state is one large object, and feature states register additional keys and values in that object. + +Looking at an example state object, you see how a feature state allows your state to be built up incrementally. Let's start with an empty state object. + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; + +@NgModule({ + imports: [StoreModule.forRoot({})], +}) +export class AppModule {} +``` + + + +Using the Standalone API: + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore } from '@ngrx/store'; + +import { AppComponent } from './app.component'; + +bootstrapApplication(AppComponent, { + providers: [provideStore()], +}); +``` + + + +This registers your application with an empty object for the root state. + +```json +{} +``` + +Now use the `scoreboard` reducer with a feature `NgModule` named `ScoreboardModule` to register additional state. + + + +```ts +export const scoreboardFeatureKey = 'game'; +``` + + + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { + scoreboardFeatureKey, + scoreboardReducer, +} from './reducers/scoreboard.reducer'; + +@NgModule({ + imports: [ + StoreModule.forFeature(scoreboardFeatureKey, scoreboardReducer), + ], +}) +export class ScoreboardModule {} +``` + + + +### Using the Standalone API + +Feature states are registered in the `providers` array of the route config. + + + +```ts +import { Route } from '@angular/router'; +import { provideState } from '@ngrx/store'; + +import { + scoreboardFeatureKey, + scoreboardReducer, +} from './reducers/scoreboard.reducer'; + +export const routes: Route[] = [ + { + path: 'scoreboard', + providers: [ + provideState({ + name: scoreboardFeatureKey, + reducer: scoreboardReducer, + }), + ], + }, +]; +``` + + + + + +**Note:** It is recommended to abstract a feature key string to prevent hardcoding strings when registering feature state and calling `createFeatureSelector`. Alternatively, you can use a [Feature Creator](guide/store/feature-creators) which automatically generates selectors for your feature state. + + + +Add the `ScoreboardModule` to the `AppModule` to load the state eagerly. + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule } from '@ngrx/store'; +import { ScoreboardModule } from './scoreboard/scoreboard.module'; + +@NgModule({ + imports: [StoreModule.forRoot({}), ScoreboardModule], +}) +export class AppModule {} +``` + + + +Using the Standalone API, register the feature state on application bootstrap: + + + +```ts +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore } from '@ngrx/store'; + +import { AppComponent } from './app.component'; +import { + scoreboardFeatureKey, + scoreboardReducer, +} from './reducers/scoreboard.reducer'; + +bootstrapApplication(AppComponent, { + providers: [ + provideStore({ [scoreboardFeatureKey]: scoreboardReducer }), + ], +}); +``` + + + +After the feature is loaded, the `game` key becomes a property in the object and is now managed in the state. + +```json +{ + "game": { "home": 0, "away": 0 } +} +``` + +Whether your feature states are loaded eagerly or lazily depends on the needs of your application. You use feature states to build up your state object over time and through different feature areas. + +## Standalone API in module-based apps + +If you have a module-based Angular application, you can still use standalone components. NgRx standalone APIs support this workflow as well. + +For module-based apps, you have the `StoreModule.forRoot({...})` included in the `imports` array of your `AppModule`, which registers the root store for dependency injection. Standalone components look for a different injection token that can only be provided by the `provideStore({...})` function detailed above. In order to use NgRx in a standalone component, you must first add the `provideStore({...})` function the the `providers` array in your `AppModule` with the same configuration you have inside of your `forRoot({...})`. For module-based apps with standalone components, you will simply have both. + + + +```ts +import { NgModule } from '@angular/core'; +import { StoreModule, provideStore } from '@ngrx/store'; +import { scoreboardReducer } from './reducers/scoreboard.reducer'; + +@NgModule({ + imports: [StoreModule.forRoot({ game: scoreboardReducer })], + providers: [provideStore({ game: scoreboardReducer })], +}) +export class AppModule {} +``` + + + +Note: Similarly, if you are using effects, you will need to register both `EffectsModule.forRoot([...])` and `provideEffects([...])`. For more info, see [Effects](guide/effects). + +## Next Steps + +Reducers are only responsible for deciding which state transitions need to occur for a given action. + +In an application there is also a need to handle impure actions, such as AJAX requests, in NgRx we call them [Effects](guide/effects). + + +# Store Selectors + +Selectors are pure functions used for obtaining slices of store state. @ngrx/store provides a few helper functions for optimizing this selection. Selectors provide many features when selecting slices of state: + +- Portability +- Memoization +- Composition +- Testability +- Type Safety + +When using the `createSelector` and `createFeatureSelector` functions @ngrx/store keeps track of the latest arguments in which your selector function was invoked. Because selectors are [pure functions](https://en.wikipedia.org/wiki/Pure_function), the last result can be returned when the arguments match without reinvoking your selector function. This can provide performance benefits, particularly with selectors that perform expensive computation. This practice is known as [memoization](https://en.wikipedia.org/wiki/Memoization). + +### Using a selector for one piece of state + + + +```ts +import { createSelector } from '@ngrx/store'; + +export interface FeatureState { + counter: number; +} + +export interface AppState { + feature: FeatureState; +} + +export const selectFeature = (state: AppState) => state.feature; + +export const selectFeatureCount = createSelector( + selectFeature, + (state: FeatureState) => state.counter +); +``` + + + +### Using selectors for multiple pieces of state + +The `createSelector` can be used to select some data from the state based on several slices of the same state. + +The `createSelector` function can take up to 8 selector functions for more complete state selections. + +For example, imagine you have a `selectedUser` object in the state. You also have an `allBooks` array of book objects. + +And you want to show all books for the current user. + +You can use `createSelector` to achieve just that. Your visible books will always be up to date even if you update them in `allBooks`. They will always show the books that belong to your user if there is one selected and will show all the books when there is no user selected. + +The result will be just some of your state filtered by another section of the state. And it will be always up to date. + + + +```ts +import { createSelector } from '@ngrx/store'; + +export interface User { + id: number; + name: string; +} + +export interface Book { + id: number; + userId: number; + name: string; +} + +export interface AppState { + selectedUser: User; + allBooks: Book[]; +} + +export const selectUser = (state: AppState) => state.selectedUser; +export const selectAllBooks = (state: AppState) => state.allBooks; + +export const selectVisibleBooks = createSelector( + selectUser, + selectAllBooks, + (selectedUser: User, allBooks: Book[]) => { + if (selectedUser && allBooks) { + return allBooks.filter( + (book: Book) => book.userId === selectedUser.id + ); + } else { + return allBooks; + } + } +); +``` + + + +The `createSelector` function also provides the ability to pass a dictionary of selectors without a projector. +In this case, `createSelector` will generate a projector function that maps the results of the input selectors to a dictionary. + +```ts +// result type - { books: Book[]; query: string } +const selectBooksPageViewModel = createSelector({ + books: selectBooks, // result type - Book[] + query: selectQuery, // result type - string +}); +``` + +### Using selectors with props + + + +Selectors with props are [deprecated](https://github.com/ngrx/platform/issues/2980). + + + +To select a piece of state based on data that isn't available in the store you can pass `props` to the selector function. These `props` gets passed through every selector and the projector function. +To do so we must specify these `props` when we use the selector inside our component. + +For example if we have a counter and we want to multiply its value, we can add the multiply factor as a `prop`: + +The last argument of a selector or a projector is the `props` argument, for our example it looks as follows: + + + +```ts +export const selectCount = createSelector( + selectCounterValue, + (counter, props) => counter * props.multiply +); +``` + + + +Inside the component we can define the `props`: + + + +```ts +ngOnInit() { + this.counter = this.store.select(fromRoot.selectCount, { multiply: 2 }) +} +``` + + + +Keep in mind that a selector only keeps the previous input arguments in its cache. If you reuse this selector with another multiply factor, the selector would always have to re-evaluate its value. This is because it's receiving both of the multiply factors (e.g. one time `2`, the other time `4`). In order to correctly memoize the selector, wrap the selector inside a factory function to create different instances of the selector. + +The following is an example of using multiple counters differentiated by `id`. + + + +```ts +export const selectCount = () => + createSelector( + (state, props) => state.counter[props.id], + (counter, props) => counter * props.multiply + ); +``` + + + +The component's selectors are now calling the factory function to create different selector instances: + + + +```ts +ngOnInit() { + this.counter2 = this.store.select(fromRoot.selectCount(), { id: 'counter2', multiply: 2 }); + this.counter4 = this.store.select(fromRoot.selectCount(), { id: 'counter4', multiply: 4 }); + this.counter6 = this.store.select(fromRoot.selectCount(), { id: 'counter6', multiply: 6 }); +} +``` + + + +## Selecting Feature States + +The `createFeatureSelector` is a convenience method for returning a top level feature state. It returns a typed selector function for a feature slice of state. + + + +```ts +import { createSelector, createFeatureSelector } from '@ngrx/store'; + +export const featureKey = 'feature'; + +export interface FeatureState { + counter: number; +} + +export const selectFeature = + createFeatureSelector(featureKey); + +export const selectFeatureCount = createSelector( + selectFeature, + (state: FeatureState) => state.counter +); +``` + + + + + +Using a [Feature Creator](guide/store/feature-creators) generates the top-level selector and child selectors for each feature state property. + + + +## Resetting Memoized Selectors + +The selector function returned by calling `createSelector` or `createFeatureSelector` initially has a memoized value of `null`. After a selector is invoked the first time its memoized value is stored in memory. If the selector is subsequently invoked with the same arguments it will return the memoized value. If the selector is then invoked with different arguments it will recompute and update its memoized value. Consider the following: + + + +```ts +import { createSelector } from '@ngrx/store'; + +export interface State { + counter1: number; + counter2: number; +} + +export const selectCounter1 = (state: State) => state.counter1; +export const selectCounter2 = (state: State) => state.counter2; +export const selectTotal = createSelector( + selectCounter1, + selectCounter2, + (counter1, counter2) => counter1 + counter2 +); // selectTotal has a memoized value of null, because it has not yet been invoked. + +let state = { counter1: 3, counter2: 4 }; + +selectTotal(state); // computes the sum of 3 & 4, returning 7. selectTotal now has a memoized value of 7 +selectTotal(state); // does not compute the sum of 3 & 4. selectTotal instead returns the memoized value of 7 + +state = { ...state, counter2: 5 }; + +selectTotal(state); // computes the sum of 3 & 5, returning 8. selectTotal now has a memoized value of 8 +``` + + + +A selector's memoized value stays in memory indefinitely. If the memoized value is, for example, a large dataset that is no longer needed it's possible to reset the memoized value to null so that the large dataset can be removed from memory. This can be accomplished by invoking the `release` method on the selector. + + + +```ts +selectTotal(state); // returns the memoized value of 8 +selectTotal.release(); // memoized value of selectTotal is now null +``` + + + +Releasing a selector also recursively releases any ancestor selectors. Consider the following: + + + +```ts +export interface State { + evenNums: number[]; + oddNums: number[]; +} + +export const selectSumEvenNums = createSelector( + (state: State) => state.evenNums, + (evenNums) => evenNums.reduce((prev, curr) => prev + curr) +); +export const selectSumOddNums = createSelector( + (state: State) => state.oddNums, + (oddNums) => oddNums.reduce((prev, curr) => prev + curr) +); +export const selectTotal = createSelector( + selectSumEvenNums, + selectSumOddNums, + (evenSum, oddSum) => evenSum + oddSum +); + +selectTotal({ + evenNums: [2, 4], + oddNums: [1, 3], +}); + +/** + * Memoized Values before calling selectTotal.release() + * selectSumEvenNums 6 + * selectSumOddNums 4 + * selectTotal 10 + */ + +selectTotal.release(); + +/** + * Memoized Values after calling selectTotal.release() + * selectSumEvenNums null + * selectSumOddNums null + * selectTotal null + */ +``` + + + +## Using Store Without Type Generic + +The most common way to select information from the store is to use a selector function defined with `createSelector`. TypeScript is able to automatically infer types from `createSelector`, which reduces the need to provide the shape of the state to `Store` via a generic argument. + +So, when injecting `Store` into components and other injectables, the generic type can be omitted. If injected without the generic, the default generic applied is `Store`. + + + +It is important to continue to provide a Store type generic if you are using the string version of selectors as types cannot be inferred automatically in those instances. + + + +The follow example demonstrates the use of `Store` without providing a generic: + + + +```ts +export class AppComponent { + counter$ = this.store.select(fromCounter.selectCounter); + + constructor(private readonly store: Store) {} +} +``` + + + +When using strict mode, the `select` method will expect to be passed a selector whose base selects from an `object`. + +This is the default behavior of `createFeatureSelector` when providing only one generic argument: + + + +```ts +import { createSelector, createFeatureSelector } from '@ngrx/store'; + +export const featureKey = 'feature'; + +export interface FeatureState { + counter: number; +} + +// selectFeature will have the type MemoizedSelector +export const selectFeature = + createFeatureSelector(featureKey); + +// selectFeatureCount will have the type MemoizedSelector +export const selectFeatureCount = createSelector( + selectFeature, + (state) => state.counter +); +``` + + + +## Using Signal Selector + +The `selectSignal` method expects a selector as an input argument and returns a signal of the selected state slice. It has a similar signature to the `select` method, but unlike `select`, `selectSignal` returns a signal instead of an observable. + +### Example Usage in Components + +```typescript +import { Component, inject } from '@angular/core'; +import { NgFor } from '@angular/common'; +import { Store } from '@ngrx/store'; + +import { selectUsers } from './users.selectors'; + +@Component({ + standalone: true, + imports: [NgFor], + template: ` +

    Users

    +
      +
    • + {{ user.name }} +
    • +
    + `, +}) +export class UsersComponent { + private readonly store = inject(Store); + + // type: Signal + readonly users = this.store.selectSignal(selectUsers); +} +``` + +### Selecting with Equality Function + +Similar to the `computed` function, the `selectSignal` method also accepts the equality function to stop the recomputation of the deeper dependency chain if two values are determined to be equal. + +## Advanced Usage + +Selectors empower you to compose a [read model for your application state](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs#solution). +In terms of the CQRS architectural pattern, NgRx separates the read model (selectors) from the write model (reducers). +An advanced technique is to combine selectors with [RxJS pipeable operators](https://rxjs.dev/guide/v6/pipeable-operators). + +This section covers some basics of how selectors compare to pipeable operators and demonstrates how `createSelector` and `scan` are utilized to display a history of state transitions. + +### Breaking Down the Basics + +#### Select a non-empty state using pipeable operators + +Let's pretend we have a selector called `selectValues` and the component for displaying the data is only interested in defined values, i.e., it should not display empty states. + +We can achieve this behaviour by using only RxJS pipeable operators: + + + +```ts +import { map, filter } from 'rxjs/operators'; + +store + .pipe( + map((state) => selectValues(state)), + filter((val) => val !== undefined) + ) + .subscribe(/* .. */); +``` + + + +The above can be further rewritten to use the `select()` utility function from NgRx: + + + +```ts +import { select } from '@ngrx/store'; +import { map, filter } from 'rxjs/operators'; + +store + .pipe( + select(selectValues), + filter((val) => val !== undefined) + ) + .subscribe(/* .. */); +``` + + + +#### Solution: Extracting a pipeable operator + +To make the `select()` and `filter()` behaviour a reusable piece of code, we extract a [pipeable operator](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md) using the RxJS `pipe()` utility function: + + + +```ts +import { select } from '@ngrx/store'; +import { pipe } from 'rxjs'; +import { filter } from 'rxjs/operators'; + +export const selectFilteredValues = pipe( + select(selectValues), + filter((val) => val !== undefined) +); + +store.pipe(selectFilteredValues).subscribe(/* .. */); +``` + + + +### Advanced Example: Select the last {n} state transitions + +Let's examine the technique of combining NgRx selectors and RxJS operators in an advanced example. + +In this example, we will write a selector function that projects values from two different slices of the application state. +The projected state will emit a value when both slices of state have a value. +Otherwise, the selector will emit an `undefined` value. + + + +```ts +export const selectProjectedValues = createSelector( + selectFoo, + selectBar, + (foo, bar) => { + if (foo && bar) { + return { foo, bar }; + } + + return undefined; + } +); +``` + + + +Then, the component should visualize the history of state transitions. +We are not only interested in the current state but rather like to display the last `n` pieces of state. +Meaning that we will map a stream of state values (`1`, `2`, `3`) to an array of state values (`[1, 2, 3]`). + + + +```ts +// The number of state transitions is given by the user (subscriber) +export const selectLastStateTransitions = (count: number) => { + return pipe( + // Thanks to `createSelector` the operator will have memoization "for free" + select(selectProjectedValues), // Combines the last `count` state values in array + scan((acc, curr) => { + return [curr, ...acc].filter( + (val, index) => index < count && val !== undefined + ); + }, [] as { foo: number; bar: string }[]) // XX: Explicit type hint for the array. + // Equivalent to what is emitted by the selector + ); +}; +``` + + + +Finally, the component will subscribe to the store, telling the number of state transitions it wishes to display: + + + +```ts +// Subscribe to the store using the custom pipeable operator +store.pipe(selectLastStateTransitions(3)).subscribe(/* .. */); +``` + + + + +# Store Testing + +### Using a Mock Store + +The `provideMockStore()` function registers providers that allow you to mock out the `Store` for testing functionality that has a dependency on `Store` without setting up reducers. +You can write tests validating behaviors corresponding to the specific state snapshot easily. + + + +**Note:** All dispatched actions don't affect the state, but you can see them in the `scannedActions$` stream. + + + +Usage: + + + +```ts +import { TestBed } from '@angular/core/testing'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; +import { cold } from 'jasmine-marbles'; + +import { AuthGuard } from '../guards/auth.guard'; + +describe('Auth Guard', () => { + let guard: AuthGuard; + let store: MockStore; + const initialState = { loggedIn: false }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + // any modules needed + ], + providers: [ + AuthGuard, + provideMockStore({ initialState }), + // other providers + ], + }); + + store = TestBed.inject(MockStore); + guard = TestBed.inject(AuthGuard); + }); + + it('should return false if the user state is not logged in', () => { + const expected = cold('(a|)', { a: false }); + + expect(guard.canActivate()).toBeObservable(expected); + }); + + it('should return true if the user state is logged in', () => { + store.setState({ loggedIn: true }); + + const expected = cold('(a|)', { a: true }); + + expect(guard.canActivate()).toBeObservable(expected); + }); +}); +``` + + + +### Using Mock Selectors + +`MockStore` also provides the ability to mock individual selectors to return a passed value using the `overrideSelector()` method. When the selector is invoked by the `select` method, the returned value is overridden by the passed value, regardless of the current state in the store. + +`overrideSelector()` returns a `MemoizedSelector`. To update the mock selector to return a different value, use the `MemoizedSelector`'s `setResult()` method. Updating a selector's mock value will not cause it to emit automatically. To trigger an emission from all selectors, use the `MockStore.refreshState()` method after updating the desired selectors. + +`overrideSelector()` supports mocking the `select` method (used in RxJS pipe) and the `Store` `select` instance method using a string or selector. + +Usage: + + + +`ts` + + + + + +`ts` + + + +In this example based on the [walkthrough](guide/store/walkthrough), we mock the `selectBooks` selector by using `overrideSelector`, passing in the `selectBooks` selector with a default mocked return value of an array of books. Similarly, we mock the `selectBookCollection` selector and pass the selector together with another array. In the test, we use `setResult()` to update the mock selectors to return new array values, then we use `MockStore.refreshState()` to trigger an emission from the `selectBooks` and `selectBookCollection` selectors. + +You can reset selectors by calling the `MockStore.resetSelectors()` method in the `afterEach()` hook. + + + +`ts` + + + +Try the . + +### Integration Testing + +An integration test should verify that the `Store` coherently works together with our components and services that inject `Store`. An integration test will not mock the store or individual selectors, as unit tests do, but will instead integrate a `Store` by using `StoreModule.forRoot` in your `TestBed` configuration. Here is part of an integration test for the `AppComponent` introduced in the [walkthrough](guide/store/walkthrough). + + + + +The integration test sets up the dependent `Store` by importing the `StoreModule`. In this part of the example, we assert that clicking the `add` button dispatches the corresponding action and is correctly emitted by the `collection` selector. + + + + +### Testing selectors + +You can use the projector function used by the selector by accessing the `.projector` property. The following example tests the `books` selector from the [walkthrough](guide/store/walkthrough). + + + + +### Testing reducers + +The following example tests the `booksReducer` from the [walkthrough](guide/store/walkthrough). In the first test we check that the state returns the same reference when the reducer is not supposed to handle the action (unknown action). The second test checks that `retrievedBookList` action updates the state and returns the new instance of it. + + + +`ts` + + + +### Testing without `TestBed` + +The `provideMockStore()` function can be also used with `Injector.create`: + + + +```ts +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { Injector } from '@angular/core'; + +describe('Books Component', () => { + let store: MockStore; + const initialState = { books: ['Book 1', 'Book 2', 'Book 3'] }; + + beforeEach(() => { + const injector = Injector.create({ + providers: [provideMockStore({ initialState })], + }); + + store = injector.get(MockStore); + }); +}); +``` + + + +Another option to create the `MockStore` without `TestBed` is by calling the `createMockStore()` function: + + + +```ts +import { MockStore, createMockStore } from '@ngrx/store/testing'; + +describe('Books Component', () => { + let store: MockStore; + const initialState = { books: ['Book 1', 'Book 2', 'Book 3'] }; + + beforeEach(() => { + store = createMockStore({ initialState }); + }); +}); +``` + + + + +# Store Walkthrough + +The following example more extensively utilizes the key concepts of store to manage the state of book list, and how the user can add a book to and remove it from their collection within an Angular component. + +## Tutorial + +1. Generate a new project using StackBlitz and create a folder named `book-list` inside the `app` folder. This folder is used to hold the book list component later in the tutorial. For now, let's start with adding a file named `books.model.ts` to reference different aspects of a book in the book list. + + + +```ts +export interface Book { + id: string; + volumeInfo: { + title: string; + authors: Array; + }; +} +``` + + + +2. Right click on the `app` folder to create a state management folder `state`. Within the new folder, create a new file `books.actions.ts` to describe the book actions. Book actions include the book list retrieval, and the add and remove book actions. + + + +```ts +import { createActionGroup, props } from '@ngrx/store'; +import { Book } from '../book-list/books.model'; + +export const BooksActions = createActionGroup({ + source: 'Books', + events: { + 'Add Book': props<{ bookId: string }>(), + 'Remove Book': props<{ bookId: string }>(), + }, +}); + +export const BooksApiActions = createActionGroup({ + source: 'Books API', + events: { + 'Retrieved Book List': props<{ books: ReadonlyArray }>(), + }, +}); +``` + + + +3. Right click on the `state` folder and create a new file labeled `books.reducer.ts`. Within this file, define a reducer function to handle the retrieval of the book list from the state and consequently, update the state. + + + +```ts +import { createReducer, on } from '@ngrx/store'; + +import { BooksApiActions } from './books.actions'; +import { Book } from '../book-list/books.model'; + +export const initialState: ReadonlyArray = []; + +export const booksReducer = createReducer( + initialState, + on(BooksApiActions.retrievedBookList, (_state, { books }) => books) +); +``` + + + +4. Create another file named `collection.reducer.ts` in the `state` folder to handle actions that alter the user's book collection. Define a reducer function that handles the add action by appending the book's ID to the collection, including a condition to avoid duplicate book IDs. Define the same reducer to handle the remove action by filtering the collection array with the book ID. + + + +```ts +import { createReducer, on } from '@ngrx/store'; +import { BooksActions } from './books.actions'; + +export const initialState: ReadonlyArray = []; + +export const collectionReducer = createReducer( + initialState, + on(BooksActions.removeBook, (state, { bookId }) => + state.filter((id) => id !== bookId) + ), + on(BooksActions.addBook, (state, { bookId }) => { + if (state.indexOf(bookId) > -1) return state; + + return [...state, bookId]; + }) +); +``` + + + +5. Import the `provideStore` from `@ngrx/store` and the `books.reducer` and `collection.reducer` file. + + + +```ts +import { provideStore } from '@ngrx/store'; + +import { booksReducer } from './state/books.reducer'; +import { collectionReducer } from './state/collection.reducer'; +``` + + + +6. Add the `provideStore` function in the `providers` array of your `app.config.ts` with an object containing the `books` and `booksReducer`, as well as the `collection` and `collectionReducer` that manage the state of the book list and the collection. The `provideStore` function registers the global providers needed to access the `Store` throughout your application. + + + +```ts +import { ApplicationConfig } from '@angular/core'; + +export const appConfig: ApplicationConfig = { + providers: [ + // ..other providers + provideStore({ + books: booksReducer, + collection: collectionReducer, + }), + ], +}; +``` + + + +7. Create the book list and collection selectors to ensure we get the correct information from the store. As you can see, the `selectBookCollection` selector combines two other selectors in order to build its return value. + + + +```ts +import { createSelector, createFeatureSelector } from '@ngrx/store'; +import { Book } from '../book-list/books.model'; + +export const selectBooks = + createFeatureSelector>('books'); + +export const selectCollectionState = + createFeatureSelector>('collection'); + +export const selectBookCollection = createSelector( + selectBooks, + selectCollectionState, + (books, collection) => { + return collection.map( + (id) => books.find((book) => book.id === id)! + ); + } +); +``` + + + +8. In the `book-list` folder, we want to have a service that fetches the data needed for the book list from an API. Create a file in the `book-list` folder named `books.service.ts`, which will call the Google Books API and return a list of books. + + + +```ts +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Book } from './books.model'; + +@Injectable({ providedIn: 'root' }) +export class GoogleBooksService { + constructor(private http: HttpClient) {} + + getBooks(): Observable> { + return this.http + .get<{ items: Book[] }>( + 'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks' + ) + .pipe(map((books) => books.items || [])); + } +} +``` + + + +9. In the same folder (`book-list`), create the `BookListComponent` with the following template. Update the `BookListComponent` class to dispatch the `add` event. + + + +```angular-ts +@for(book of books; track book) { +
    +

    {{book.volumeInfo.title}}

    by {{book.volumeInfo.authors}} + +
    +} +``` + +
    + + + +```ts +import { Component, input, output } from '@angular/core'; +import { Book } from './books.model'; + +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.css'], +}) +export class BookListComponent { + books = input>([]); + add = output(); +} +``` + + + +10. Create a new _Component_ named `book-collection` in the `app` folder. Update the `BookCollectionComponent` template and class. + + + +```angular-ts +@for(book of books; track book) { +
    +

    {{book.volumeInfo.title}}

    by {{book.volumeInfo.authors}} + +
    +} +``` + +
    + + + +```ts +import { Component, input, output } from '@angular/core'; +import { Book } from '../book-list/books.model'; + +@Component({ + selector: 'app-book-collection', + templateUrl: './book-collection.component.html', + styleUrls: ['./book-collection.component.css'], +}) +export class BookCollectionComponent { + books = input>([]); + add = output(); +} +``` + + + +11. Add `BookListComponent` and `BookCollectionComponent` to your `AppComponent` template, and to your imports in `app.component.ts` as well. + + + +```html +

    Books

    + + + + +

    My Collection

    + + + +``` + +
    + +```ts +import { Component, input, output } from '@angular/core'; +import { Book } from '../book-list/books.model'; + +@Component({ + selector: 'app-book-collection', + templateUrl: './book-collection.component.html', + styleUrls: ['./book-collection.component.css'], + imports: [BookListComponent, BookCollectionComponent], +}) +export class BookCollectionComponent { + books = input>([]); + add = output(); +} +``` + +
    + +12. In the `AppComponent` class, add the selectors and corresponding actions to dispatch on `add` or `remove` method calls. Then subscribe to the Google Books API in order to update the state. (This should probably be handled by NgRx Effects, which you can read about [here](guide/effects). For the sake of this demo, NgRx Effects is not being included). + + + +```ts +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import { + selectBookCollection, + selectBooks, +} from './state/books.selectors'; +import { BooksActions, BooksApiActions } from './state/books.actions'; +import { GoogleBooksService } from './book-list/books.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', +}) +export class AppComponent implements OnInit { + books = this.store.selectSignal(selectBooks); + bookCollection = this.store.selectSignal(selectBookCollection); + + onAdd(bookId: string) { + this.store.dispatch(BooksActions.addBook({ bookId })); + } + + onRemove(bookId: string) { + this.store.dispatch(BooksActions.removeBook({ bookId })); + } + + constructor( + private booksService: GoogleBooksService, + private store: Store + ) {} + + ngOnInit() { + this.booksService + .getBooks() + .subscribe((books) => + this.store.dispatch( + BooksApiActions.retrievedBookList({ books }) + ) + ); + } +} +``` + + + +And that's it! Click the add and remove buttons to change the state. + +Let's cover what you did: + +- Defined actions to express events. +- Defined two reducer functions to manage different parts of the state. +- Registered the global state container that is available throughout your application. +- Defined the state, as well as selectors that retrieve specific parts of the state. +- Created two distinct components, as well as a service that fetches from the Google Books API. +- Injected the `Store` and Google Books API services to dispatch actions and select the current state. + + +# Why use NgRx Store for State Management? + +NgRx Store provides state management for creating maintainable, explicit applications through the use of single state and actions in order to express state changes. In cases where you don't need a global, application-wide solution to manage state, consider using [NgRx Signals](guide/signals) which provides a solution for local state management. + +## When Should I Use NgRx Store for State Management? + +In particular, you might use NgRx when you build an application with a lot of user interactions and multiple data sources, or when managing state in services are no longer sufficient. + +A good guideline that might help answer the question, "Do I need NgRx Store?" is the +**SHARI** principle: + +- **S**hared: state that is accessed by many components and services. + +- **H**ydrated: state that is persisted and rehydrated from external storage. + +- **A**vailable: state that needs to be available when re-entering routes. + +- **R**etrieved: state that must be retrieved with a side-effect. + +- **I**mpacted: state that is impacted by actions from other sources. + +However, realizing that using NgRx Store comes with some tradeoffs is also crucial. It is not meant to be the shortest or quickest way to write code. It also encourages the usage of many files. + +It's also important to consider the patterns implemented with NgRx Store. A solid understanding of [`RxJS`](https://rxjs.dev) and [`Redux`](https://redux.js.org/) will be very beneficial before learning to use NgRx Store and the other state management libraries. + +## Key Concepts + +### Type Safety + +Type safety is promoted throughout the architecture with reliance on the TypeScript compiler for program correctness. In addition to this, NgRx's strictness of type safety and the use of patterns lends itself well to the creation of higher quality code. + +### Immutability and Performance + +[Store](guide/store) is built on a single, immutable data structure which makes change detection a relatively straightforward task using the [`OnPush`](https://angular.dev/api/core/ChangeDetectionStrategy#OnPush) strategy. NgRx Store also provides APIs for creating memoized selector functions that optimize retrieving data from your state. + +### Encapsulation + +Using NgRx [Effects](guide/effects) and [Store](guide/store), any interaction with external resources side effects such as network requests or web sockets, as well as any business logic, can be isolated from the UI. This isolation allows for more pure and simple components and upholds the single responsibility principle. + +### Serializability + +By normalizing state changes and passing them through observables, NgRx provides serializability and ensures the state is predictably stored. This allows the state to be saved to external storage such as `localStorage`. + +This also allows the inspection, download, upload, and the dispatch of actions all from the [Store Devtools](guide/store-devtools). + +### Testable + +Because [Store](guide/store) uses pure functions for changing and selecting data from state, as well as the ability to isolate side effects from the UI, testing becomes very straightforward. +NgRx also provides test resources such as `provideMockStore` and `provideMockActions` for isolated tests and an overall better test experience. + + +# Runtime checks + +Runtime checks are here to guide developers to follow the NgRx and Redux core concepts and best practices. They are here to shorten the feedback loop of easy-to-make mistakes when you're starting to use NgRx, or even a well-seasoned developer might make. During development, when a rule is violated, an error is thrown notifying you what and where something went wrong. + +`@ngrx/store` ships with six (6) built-in runtime checks: + +- Default On: + - [`strictStateImmutability`](#strictstateimmutability): verifies that the state isn't mutated. + - [`strictActionImmutability`](#strictactionimmutability): verifies that actions aren't mutated +- Default Off: + - [`strictStateSerializability`](#strictstateserializability): verifies if the state is serializable + - [`strictActionSerializability`](#strictactionserializability): verifies if the actions are serializable + - [`strictActionWithinNgZone`](#strictactionwithinngzone): verifies if actions are dispatched within NgZone + - [`strictActionTypeUniqueness`](#strictactiontypeuniqueness): verifies if registered action types are unique + +All checks will automatically be disabled in production builds. + +## Configuring runtime checks + +It's possible to override the default configuration of runtime checks. To do so, use the `runtimeChecks` property on the root store's config object. For each runtime check you can toggle the check with a `boolean`, `true` to enable the check, `false` to disable the check. + +```ts +@NgModule({ + imports: [ + StoreModule.forRoot(reducers, { + runtimeChecks: { + strictStateImmutability: true, + strictActionImmutability: true, + strictStateSerializability: true, + strictActionSerializability: true, + strictActionWithinNgZone: true, + strictActionTypeUniqueness: true, + }, + }), + ], +}) +export class AppModule {} +``` + + + +The serializability runtime checks cannot be enabled if you use `@ngrx/router-store` with the `FullRouterStateSerializer`. The [full serializer](guide/router-store/configuration) has an unserializable router state and actions that are not serializable. To use the serializability runtime checks either use the `MinimalRouterStateSerializer` or implement a custom router state serializer. + + + +### strictStateImmutability + +The number one rule of NgRx, immutability. This `strictStateImmutability` check verifies if a developer tries to modify the state object. This check is important to be able to work with the state in a predictable way, it should always be possible to recreate the state. + +Example violation of the rule: + +```ts +export const reducer = createReducer( + initialState, + on(addTodo, (state, { todo }) => { + // Violation 1: we assign a new value to `todoInput` directly + (state.todoInput = ''), + // Violation 2: `push` modifies the array + state.todos.push(todo); + }) +); +``` + +To fix the above violation, a new reference to the state has to be created: + +```ts +export const reducer = createReducer( + initialState, + on(addTodo, (state, { todo }) => ({ + ...state, + todoInput: '', + todos: [...state.todos, todo], + })) +); +``` + +### strictActionImmutability + +Uses the same check as `strictStateImmutability`, but for actions. An action should not be modified. + +Example violation of the rule: + +```ts +export const reducer = createReducer( + initialState, + on(addTodo, (state, { todo }) => { + // Violation, it's not allowed to modify an action + todo.id = generateUniqueId(); + return { + ...state, + todos: [...state.todos, todo], + }; + }) +); +``` + +To fix the above violation, the todo's id should be set in the action creator or should be set in an immutable way. That way we can simply append the todo to the current `todos`: + +```ts +export const addTodo = createAction( + '[Todo List] Add Todo', + (description: string) => ({ id: generateUniqueId(), description }) +); +export const reducer = createReducer( + initialState, + on(addTodo, (state, { todo }) => ({ + ...state, + todos: [...state.todos, todo], + })) +); +``` + +### strictStateSerializability + +This check verifies if the state is serializable. A serializable state is important to be able to persist the current state to be able to rehydrate the state in the future. + +Example violation of the rule: + +```ts +export const reducer = createReducer( + initialState, + on(completeTodo, (state, { id }) => ({ + ...state, + todos: { + ...state.todos, + [id]: { + ...state.todos[id], + // Violation, Date is not serializable + completedOn: new Date(), + }, + }, + })) +); +``` + +As a fix of the above violation the `Date` object must be made serializable: + +```ts +export const reducer = createReducer( + initialState, + on(completeTodo, (state, { id }) => ({ + ...state, + todos: { + ...state.todos, + [id]: { + ...state.todos[id], + completedOn: new Date().toJSON(), + }, + }, + })) +); +``` + +### strictActionSerializability + +The `strictActionSerializability` check resembles `strictStateSerializability` but as the name says, it verifies if the action is serializable. An action must be serializable to be replayed, this can be helpful during development while using the Redux DevTools and in production to be able to debug errors. + +Example violation of the rule: + +```ts +const createTodo = createAction( + '[Todo List] Add new todo', + (todo) => ({ + todo, + // Violation, a function is not serializable + logTodo: () => { + console.log(todo); + }, + }) +); +``` + +The fix for this violation is to not add functions on actions, as a replacement a function can be created: + +```ts +const createTodo = createAction( + '[Todo List] Add new todo', + props<{ todo: Todo }>() +); + +function logTodo(todo: Todo) { + console.log(todo); +} +``` + + + +Please note, you may not need to set `strictActionSerializability` to `true` unless you are storing/replaying actions using external resources, for example `localStorage`. + + + +### strictActionWithinNgZone + +The `strictActionWithinNgZone` check verifies that Actions are dispatched by asynchronous tasks running within `NgZone`. Actions dispatched by tasks, running outside of `NgZone`, will not trigger ChangeDetection upon completion and may result in a stale view. + +Example violation of the rule: + +```ts +// Callback running outside of NgZone +function callbackOutsideNgZone() { + this.store.dispatch(clearTodos()); +} +``` + +To fix ensure actions are running within `NgZone`. Identify the event trigger and then verify if the code can be updated to use a `NgZone` aware feature. If this is not possible use the `NgZone.run` method to explicitly run the asynchronous task within NgZone. + +```ts +import { NgZone } from '@angular/core'; + +constructor(private ngZone: NgZone){} + +// Callback running outside of NgZone brought back in NgZone. +function callbackOutsideNgZone(){ + this.ngZone.run(() => { + this.store.dispatch(clearTodos()); + } +} +``` + +### strictActionTypeUniqueness + +The `strictActionTypeUniqueness` guards you against registering the same action type more than once. + +Example violation of the rule: + +```ts +export const customerPageLoaded = createAction( + '[Customers Page] Loaded' +); +export const customerPageRefreshed = createAction( + '[Customers Page] Loaded' +); +``` + +The fix of the violation is to create unique action types: + +```ts +export const customerPageLoaded = createAction( + '[Customers Page] Loaded' +); +export const customerPageRefreshed = createAction( + '[Customers Page] Refreshed' +); +``` + + +# Using Store in AngularJS + +If you are working on an AngularJS to Angular conversion, you can use +`@ngrx/store` to provide global state to your hybrid application. + +## Downgrading Store service + +If you want to **dispatch** an action or **select** some slice of your store +state, you will need to downgrade the Store service to use it in the AngularJS +parts of your application. + + + +```ts +import { Store } from '@ngrx/store'; +import { downgradeInjectable } from '@angular/upgrade/static'; +import { module as ngModule } from 'angular'; +// app +import { MyActionClass } from 'path/to.my/file.action'; +import { mySelectorFunction } from 'path/to.my/file.selector'; + +// Using the `downgradeInjectable` to create the `ngrxStoreService` factory in AngularJS +ngModule('appName').factory( + 'ngrxStoreService', + downgradeInjectable(Store) +); + +// AngularJS controller +export default ngModule('appName').controller('AngularJSController', [ + '$scope', + '$controller', + 'ngrxStoreService', + function ($scope, $controller, ngrxStoreService) { + // ... + ngrxStoreService.dispatch(new MyActionClass(myPayload)); + ngrxStoreService.select(mySelectorFunction).subscribe(/*...*/); + // ... + }, +]); +``` + + + + +# Using Dependency Injection + +## Injecting Reducers + +To inject the root reducers into your application, use an `InjectionToken` and a `Provider` to register the reducers through dependency injection. + + + +```ts +import { NgModule, inject, InjectionToken } from '@angular/core'; +import { StoreModule, ActionReducerMap } from '@ngrx/store'; + +import { SomeService } from './some.service'; +import * as fromRoot from './reducers'; + +export const REDUCER_TOKEN = new InjectionToken< + ActionReducerMap +>('Registered Reducers', { + factory: () => { + const serv = inject(SomeService); + // return reducers synchronously + return serv.getReducers(); + }, +}); + +@NgModule({ + imports: [StoreModule.forRoot(REDUCER_TOKEN)], +}) +export class AppModule {} +``` + + + +Reducers are also injected when composing state through feature modules. + + + +```ts +import { NgModule, InjectionToken } from '@angular/core'; +import { StoreModule, ActionReducerMap } from '@ngrx/store'; + +import * as fromFeature from './reducers'; + +export const FEATURE_REDUCER_TOKEN = new InjectionToken< + ActionReducerMap +>('Feature Reducers'); + +export function getReducers(): ActionReducerMap { + // map of reducers + return {}; +} + +@NgModule({ + imports: [ + StoreModule.forFeature( + fromFeature.featureKey, + FEATURE_REDUCER_TOKEN + ), + ], + providers: [ + { + provide: FEATURE_REDUCER_TOKEN, + useFactory: getReducers, + }, + ], +}) +export class FeatureModule {} +``` + + + +## Injecting Meta-Reducers + +To inject 'middleware' meta reducers, use the `META_REDUCERS` injection token exported in +the Store API and a `Provider` to register the meta reducers through dependency +injection. + + + +```ts +import { + ActionReducer, + MetaReducer, + META_REDUCERS, +} from '@ngrx/store'; +import { SomeService } from './some.service'; +import * as fromRoot from './reducers'; + +export function metaReducerFactory(): MetaReducer { + return (reducer: ActionReducer) => (state, action) => { + console.log('state', state); + console.log('action', action); + return reducer(state, action); + }; +} + +@NgModule({ + providers: [ + { + provide: META_REDUCERS, + deps: [SomeService], + useFactory: metaReducerFactory, + multi: true, + }, + ], +}) +export class AppModule {} +``` + + + + + +Careful attention should be called to the use of the `multi` +property in the provider here for `META_REDUCERS`. As this injection token may be utilized +by many libraries concurrently, specifying `multi: true` is critical to ensuring that all +library meta reducers are applied to any project that consumes multiple NgRx libraries with +registered meta reducers. + + + +## Injecting Feature Config + +To inject the feature store configuration into your module, use an `InjectionToken` and a `Provider` to register the feature config object through dependency injection. + + + +```ts +import { NgModule, InjectionToken } from '@angular/core'; +import { StoreModule, StoreConfig } from '@ngrx/store'; +import { SomeService } from './some.service'; + +import * as fromFeature from './reducers'; + +export const FEATURE_CONFIG_TOKEN = new InjectionToken< + StoreConfig +>('Feature Config'); + +export function getConfig( + someService: SomeService +): StoreConfig { + // return the config synchronously. + return { + initialState: someService.getInitialState(), + + metaReducers: [ + fromFeature.loggerFactory(someService.loggerConfig()), + ], + }; +} + +@NgModule({ + imports: [ + StoreModule.forFeature( + fromFeature.featureKey, + fromFeature.reducers, + FEATURE_CONFIG_TOKEN + ), + ], + providers: [ + { + provide: FEATURE_CONFIG_TOKEN, + deps: [SomeService], + useFactory: getConfig, + }, + ], +}) +export class FeatureModule {} +``` + + + + +# Instrumentation options + +When you call the instrumentation, you can give an optional configuration object. As stated, each property in the object provided is optional. + +## Configuration Object Properties + +### `maxAge` + +number (>1) | `false` - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. Default is `false` (infinite). + +### `logOnly` + +boolean - connect to the Devtools Extension in log-only mode. Default is `false` which enables all extension [features](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features). + +### `autoPause` + +boolean - Pauses recording actions and state changes when the extension window is not open. Default is `false`. + +### `name` + +string - the instance name to show on the monitor page. Default value is NgRx Store DevTools. + +### `monitor` + +function - the monitor function configuration that you want to hook. + +### `actionSanitizer` + +function - takes `action` object and id number as arguments, and should return an `action` object. + +### `stateSanitizer` + +function - takes `state` object and index as arguments, and should return a `state` object. + +### `serialize` + +- options + - `undefined` - will use regular `JSON.stringify` to send data + - `false` - will handle also circular references + - `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions + - object - which contains `date`, `regex`, `undefined`, `NaN`, `infinity`, `Error`, `Symbol`, `Map`, `Set` and `function` keys. For each of them, you can indicate if they have to be included by setting them to `true`. For function keys, you can also specify a custom function which handles serialization. + +For more detailed information see [Redux DevTools Serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize) + +### `actionsSafelist` / `actionsBlocklist` + +array of strings as regex - actions types to be hidden / shown in the monitors, [more information here](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#actionsblacklist--actionswhitelist). + +### `predicate` + +function - called for every action before sending, takes state and action object, and returns `true` in case it allows sending the current data to the monitor, [more information here](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#predicate). + +### `connectInZone` + +boolean - property determines whether the extension connection is established within the Angular zone or not. When `false`, the connection is established outside the Angular zone to prevent unnecessary change detection cycles. Default is `false`. + +### `features` + +configuration object - containing properties for features than can be enabled or disabled in the browser extension Redux DevTools. These options are passed through to the browser extension verbatim. By default, all features are enabled. For more information visit the [Redux DevTools Docs](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#features) + +```typescript +features: { + pause: true, // start/pause recording of dispatched actions + lock: true, // lock/unlock dispatching actions and side effects + persist: true, // persist states on page reloading + export: true, // export history of actions in a file + import: 'custom', // import history of actions from a file + jump: true, // jump back and forth (time travelling) + skip: true, // skip (cancel) actions + reorder: true, // drag and drop actions in the history list + dispatch: true, // dispatch custom actions or action creators + test: true // generate tests for the selected actions +}, +``` + +## Example Object as provided in module imports + + + +```ts +export const appConfig: ApplicationConfig = { + providers: [ + provideStoreDevtools({ + maxAge: 25, + logOnly: false, + autoPause: true, + features: { + pause: false, + lock: true, + persist: true, + }, + }), + ], +}; +``` + + + + + +An example of the `@ngrx/store-devtools` setup in module-based applications is available at the [following link](https://v17.ngrx.io/guide/store-devtools). + + + + +# @ngrx/store-devtools + +Store Devtools provides developer tools and instrumentation for [Store](guide/store). + +## Installation + +Detailed installation instructions can be found on the [Installation](guide/store-devtools/install) page. + +## Setup + +Instrumentation with the Chrome / Firefox Extension + +1. Download the [Redux Devtools Extension](https://github.com/reduxjs/redux-devtools/) + +2. Provide the `provideStoreDevtools` to the application config: + + + +```ts +import { isDevMode } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideStore } from '@ngrx/store'; +import { provideStoreDevtools } from '@ngrx/store-devtools'; + +import { AppComponent } from './app.component'; + +bootstrapApplication(AppComponent, { + providers: [ + provideStore(), + provideStoreDevtools({ + maxAge: 25, // Retains last 25 states + logOnly: !isDevMode(), // Restrict extension to log-only mode + autoPause: true, // Pauses recording actions and state changes when the extension window is not open + trace: false, // If set to true, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code + traceLimit: 75, // maximum stack trace frames to be stored (in case trace option was provided as true) + connectInZone: true, // If set to true, the connection is established within the Angular zone + }), + ], +}); +``` + + + +> More extension options and explanation, refer to [Redux Devtools Documentation](https://github.com/reduxjs/redux-devtools#documentation) + + +# Store Devtools Installation + +## Installing with `ng add` + +You can install the Store Devtools to your project with the following `ng add` command (details here): + +```sh +ng add @ngrx/store-devtools@latest +``` + +### Optional `ng add` flags + +| flag | description | value type | default value | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------- | +| `--path` | Path to the module that you wish to add the import for the `StoreDevtoolsModule` to. | `string` | +| `--project` | Name of the project defined in your `angular.json` to help locating the module to add the `StoreDevtoolsModule` to. | `string` | +| `--module` | Name of file containing the module that you wish to add the import for the `StoreDevtoolsModule` to. Can also include the relative path to the file. For example, `src/app/app.module.ts`. | `string` | `app` | +| `--maxAge` | Maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. 0 is infinite. Must be greater than 1 or 0. | `number` | `25` | + +This command will automate the following steps: + +1. Update `package.json` > `dependencies` with `@ngrx/store-devtools`. +2. Run `npm install` to install those dependencies. +3. Add the devtools to the application config provider's using `provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() })`. The `maxAge` property will be set to the flag `maxAge` if provided. + +## Installing with `npm` + +For more information on using `npm` check out the docs here. + +```sh +npm install @ngrx/store-devtools --save +``` + +## Installing with `yarn` + +For more information on using `yarn` check out the docs here. + +```sh +yarn add @ngrx/store-devtools +``` + + +# Excluding Store Devtools In Production + +To prevent Store Devtools from being included in your bundle, you can exclude it from the build process. + +## Step 1: Put Store Devtools In `environment.ts` + +To exclude the DevTools, put `@ngrx/store-devtools` into an `environment.ts` file, which is replaced with `environment.prod.ts`. + + + +If the environment files don't exist in your project you can use the `ng generate environments` command to create them. + + + +Given the below example: + + + +```ts +import { provideStoreDevtools } from '@ngrx/store-devtools'; + +export const environment = { + production: false, + providers: [provideStoreDevtools({ maxAge: 25 })], +}; +``` + + + +## Step 2: Import Environment File + +Modify the `app.config.ts` file, where your application configuration resides, to specify `environment.providers`: + + + +```ts +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [provideStore(), environment.providers], +}; +``` + + \ No newline at end of file diff --git a/projects/www/public/llms.txt b/projects/www/public/llms.txt new file mode 100644 index 0000000000..b5df19843e --- /dev/null +++ b/projects/www/public/llms.txt @@ -0,0 +1,29 @@ +# NgRx + +## Overview +[NgRx](https://ngrx-site-v19.netlify.app/): NgRx is a set of reactive libraries for Angular that helps manage state and side effects in applications. It leverages the power of RxJS to provide a robust framework for building scalable applications. + +## Workshops +[co_present Workshops](https://ngrx-site-v19.netlify.app/workshops): This section provides information about workshops that focus on NgRx, offering hands-on experience and practical knowledge for developers looking to enhance their skills in state management with NgRx. + +## API Reference +[description API Reference](https://ngrx-site-v19.netlify.app/api): The API Reference documentation details the various components and functionalities of NgRx, including the Store, Effects, and other essential features. It serves as a comprehensive guide for developers to understand how to implement and utilize NgRx in their applications. + +## Support +[help Support](https://ngrx-site-v19.netlify.app/support): This section offers support resources for NgRx users, including FAQs, troubleshooting tips, and links to community forums where developers can seek help and share knowledge. + +## Code Repository +[code GitHub](https://github.com/ngrx/platform): The GitHub repository for NgRx contains the source code, issue tracking, and contribution guidelines. Developers can access the latest updates, report bugs, and contribute to the NgRx project. + +## Core Concepts +### Store +[Store](https://ngrx-site-v19.netlify.app/guide/store): The Store is a central feature of NgRx that holds the application state. It provides a single source of truth and allows for state management through actions and reducers. + +### Effects +[Effects](https://ngrx-site-v19.netlify.app/guide/effects): Effects are used in NgRx to handle side effects, such as API calls or other asynchronous operations. They allow developers to manage complex interactions outside of the Store. + +### Signals +[Signals](https://ngrx-site-v19.netlify.app/guide/signals): Signals in NgRx provide a way to react to changes in state or actions, enabling developers to create more dynamic and responsive applications. + +### Operators +[Operators](https://ngrx-site-v19.netlify.app/guide/operators): NgRx utilizes various RxJS operators to manipulate streams of data, allowing for powerful and flexible data handling within the application. This section covers the most commonly used operators in NgRx. \ No newline at end of file