DEV Community 👩‍💻👨‍💻

Cover image for NgRx Feature Creator
Marko Stanimirović for This is Angular

Posted on • Updated on

NgRx Feature Creator

Cover photo by Sigmund 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 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:

// 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,
  }))
);
Enter fullscreen mode Exit fullscreen mode

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

// 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 {}
Enter fullscreen mode Exit fullscreen mode

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

// 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 })
);
Enter fullscreen mode Exit fullscreen mode

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:

// `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,
    }))
  ),
});
Enter fullscreen mode Exit fullscreen mode

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

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

@NgModule({
  imports: [StoreModule.forFeature(booksFeature)],
})
export class BooksModule {}
Enter fullscreen mode Exit fullscreen mode

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

// 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 })
);
Enter fullscreen mode Exit fullscreen mode

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 introduced in TypeScript v4.1. It could bring big improvements to your code, especially with huge feature states.

Resources

Peer Reviewers

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.

Top comments (5)

Collapse
walll_e profile image
adamstret

Hello Marco,
what about featureCreator combined with ngrx-adapter? How would you auto-create selectors, when you use the adapter? I tried it and it didn't work for some reason. Is there even a way to do that?

Collapse
markostanimirovic profile image
Marko Stanimirović Author

Hi @walll_e

Take a look at this comment: github.com/ngrx/platform/issues/34...

Collapse
walll_e profile image
adamstret

Well that there is just a workaround. I assume there's no exported member from @ngrx that would facilitate this, right?

Collapse
fritzherbers profile image
FritzHerbers

Hi Marco
I am looking for some best practice guidance for the new createFuture.
github.com/ngrx/platform/pull/3033...
Can you please comment.

Collapse
markostanimirovic profile image
Marko Stanimirović Author

Hi @fritzherbers

For feature states with nested reducers, I recommend using the "traditional" way without createFeature.

DEV runs on 100% open source code known as Forem.

 
Contribute to the codebase or host your own.
 
Check these out! 👇