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,
}))
);
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 {}
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 })
);
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,
}))
),
});
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 {}
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 })
);
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)
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?
Hi @walll_e
Take a look at this comment: github.com/ngrx/platform/issues/34...
Well that there is just a workaround. I assume there's no exported member from @ngrx that would facilitate this, right?
Hi Marco
I am looking for some best practice guidance for the new createFuture.
github.com/ngrx/platform/pull/3033...
Can you please comment.
Hi @fritzherbers
For feature states with nested reducers, I recommend using the "traditional" way without createFeature.