NgRx Feature Creator
Tags: angular, ngrx, rxjs, typescript
Published: 2021-09-14 12:42:02

*Cover photo by [Sigmund](https://unsplash.com/photos/B-x4VaIriRc) on Unsplash.*

The `createFeature` function is introduced in NgRx v12.1.
It reduces repetitive code in selector files by generating a feature selector and child selectors for each feature state property. It's inspired by the [ngrx-child-selectors](https://github.com/markostanimirovic/ngrx-child-selectors) library.


## 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. Let's now look at the "traditional" way of creating the NgRx feature.

To create a reducer there is the `createReducer` function from the `@ngrx/store` package:

```ts
// books.reducer.ts
import { createReducer } from "@ngrx/store";

import * as BookListPageActions from "./book-list-page.actions";
import * as BooksApiActions from "./books-api.actions";

export const featureName = "books";

export interface State {
  books: Book[];
  loading: boolean;
}

const initialState: State = {
  books: [],
  loading: false,
};

export const reducer = createReducer(
  initialState,
  on(BookListPageActions.enter, (state) => ({
    ...state,
    loading: true,
  })),
  on(BooksApiActions.loadBooksSuccess, (state, { books }) => ({
    ...state,
    books,
    loading: false,
  }))
);
```

To register this reducer in the NgRx store, we use the `StoreModule.forFeature` method:

```ts
// books.module.ts
import { StoreModule } from "@ngrx/store";

import * as fromBooks from "./books.reducer";

@NgModule({
  imports: [
    StoreModule.forFeature(fromBooks.featureName, fromBooks.reducer),
  ],
})
export class BooksModule {}
```

To select the state from the store, let's create a feature selector, child selectors, and also a view model selector:

```ts
// books.selectors.ts
import { createFeatureSelector, createSelector } from "@ngrx/store";

import * as fromBooks from "./books.reducer";

// feature selector
export const selectBooksState = createFeatureSelector<fromBooks.State>(
  fromBooks.featureKey
);

// child selectors
export const selectBooks = createSelector(
  selectBooksState,
  (state) => state.books
);
export const selectLoading = createSelector(
  selectBooksState,
  (state) => state.loading
);

// view model selector
export const selectBookListPageViewModel = createSelector(
  selectBooks,
  selectLoading,
  (books, loading) => ({ books, loading })
);
```

## Using Feature Creator

Let's now look at how to achieve the same result by using the `createFeature` function. We will first refactor the reducer file:

```ts
// `createFeature` is imported from `@ngrx/store`
import { createFeature, createReducer } from "@ngrx/store";

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,
};

// feature name and reducer are now passed to `createFeature`
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,
    }))
  ),
});
```

Registering the feature reducer in the store can now be done by passing the entire feature object to the `StoreModule.forFeature` method:

```ts
// books.module.ts
import { StoreModule } from "@ngrx/store";
import { booksFeature } from "./books.reducer";

@NgModule({
  imports: [StoreModule.forFeature(booksFeature)],
})
export class BooksModule {}
```

Finally, let's see what the selector file looks like:

```ts
// books.selectors.ts
import { createSelector } from "@ngrx/store";
import { booksFeature } from "./books.reducer";

export const selectBookListPageViewModel = createSelector(
  booksFeature.selectBooks,
  booksFeature.selectLoading,
  (books, loading) => ({ books, loading })
);
```

The previously manually created feature and child selectors are now removed because `createFeature` generates them for us. 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.

## Conclusion

Feature creators reduce repetitive code in selector files by using the power of [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) introduced in TypeScript v4.1. It could bring big improvements to your code, especially with huge feature states.

## Resources

- [Official docs of the `createFeature` function](https://ngrx.io/api/store/createFeature)

## Peer Reviewers

- [Tim Deschryver](https://twitter.com/tim_deschryver)

Thank you Tim for giving me helpful suggestions on this article!

---

> The "NgRx Feature Creator" guide is now available in the official NgRx documentation. Read more [here](https://ngrx.io/guide/store/feature-creators).