DEV Community

Cover image for NgRx Feature Creator

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 for obtaining 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 group of 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!

Discussion (0)