createFeature NgRx - It Changes Everything
createFeature NgRx - It Changes Everything

In this post I want to talk about new feature in NgRx which is called createFeature.

Starting from Angular 15 we get inside NgRx such feature which is called createFeature.

This is just a sugar to create a reducer which allows us to write much less code than previously.

Initial project

Initial project

Here I have a small project where we get a list of posts which we fetch from API. It is all done with NgRx this is why inside Redux Devtools we can see our actions.

Now let's look on our code.

export const initialState: PostsStateInterface = {
  isLoading: false,
  posts: [],
  error: null,
};

export const reducers = createReducer(
  initialState,
  on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
  on(PostsActions.getPostsSuccess, (state, action) => ({
    ...state,
    isLoading: false,
    posts: action.posts,
  })),
  on(PostsActions.getPostsFailure, (state, action) => ({
    ...state,
    isLoading: false,
    error: action.error,
  }))
),

Here we defined initialState for our posts page and a reducers where actions change our state.

Additionally to that we have a bunch of selectors to get this data in our component.

export const selectFeature = (state: AppStateInterface) => state.posts

export const isLoadingSelector = createSelector(
  selectFeature,
  state => state.isLoading
)
export const postsSelector = createSelector(
  selectFeature,
  state => state.posts
)
export const errorSelector = createSelector(
  selectFeature,
  state => state.error
)

Refactoring to createFeature

Now let's refactor our reducer code to the new version.

const postsFeature = createFeature({
  name: 'posts',
  reducer: createReducer(
    initialState,
    on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
    on(PostsActions.getPostsSuccess, (state, action) => ({
      ...state,
      isLoading: false,
      posts: action.posts,
    })),
    on(PostsActions.getPostsFailure, (state, action) => ({
      ...state,
      isLoading: false,
      error: action.error,
    }))
  ),
});

Here instead of reducers we created a postsFeature by calling createFeature function. Inside we provided a name and a reducer which has exactly the same code as previously. So the changes are minimal. But what are the benefits?

export const {
  name: postsFeatureKey,
  reducer: postsReducer,
  selectError,
  selectIsLoading,
  selectPosts,
} = postsFeature;

Here we can export from postsFeature a lot. We get a name and reducer that we can use everywhere but also we got 3 selectors. selectError, selectIsLoading and selectPosts.

The biggest benefit of createFeature is that we get all default selectors out of the box without need to create them.

Now we can completely remove our file selectors.ts as we get all selectors from createFeature and we don't need to write any of them.

We just need to update our component and use these selectors now.

import { selectError, selectIsLoading, selectPosts } from './store/reducers';

export class PostsComponent implements OnInit {
  isLoading$ = this.store.pipe(select(selectIsLoading));
  error$ = this.store.pipe(select(selectError));
  posts$ = this.store.pipe(select(selectPosts));

  ...
}

We must also update main.ts to use the correct reducer name.

import { postsReducer, postsFeatureKey } from './app/posts/store/reducers';

bootstrapApplication(AppComponent, {
  providers: [
    provideState(postsFeatureKey, postsReducer),
    ...
  ],
});

Here we imported postsReducer and postsFeatureKey which we got from createFeature.

And actually if you want to learn Angular with NgRx from empty folder to a fully functional production application make sure to check my Angular and NgRx - Building Real Project From Scratch course.

📚 Source code of what we've done