This content originally appeared on DEV Community and was authored by Marko Stanimirović
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,
}))
);
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!
This content originally appeared on DEV Community and was authored by Marko Stanimirović
Marko Stanimirović | Sciencx (2021-09-14T12:42:02+00:00) NgRx Feature Creator. Retrieved from https://www.scien.cx/2021/09/14/ngrx-feature-creator/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.