How I’ve Set Up NgRx Feature Based in Angular 16 with Standalone Components

A guide on how to implement NgRx based on Feature Creators

Kamil Konopka
Bits and Pieces

--

Photo by Caspar Camille Rubin on Unsplash

Some time ago, I published an article related to setting up NgRx state management with Standalone Components within Angular 16. I promised then to provide one more, related to a slightly different approach (which you might have already known from Redux Toolkit or Vuex) soon.

As Redux Toolkit / Vuex implementations allow user to mutate the state, Feature Creators are not allowing that! This is the most important difference!

In case you haven’t read my previous article, where I explain the concept — Flux Architecture in general and walk through the initial setup, like installing dependencies etc.:

Here we are, Feature Creators-based NgRx state management implementation. In order to be able to make a comparison on your own and decide, which approach is more convenient, I will use the exact data structure as within the previous article.

Before we take a deeper look into actual implementation, we need to understand what Feature Creators are. Basically, it is a particular feature state grouping of name, reducer and selectors. In other words, there will be one single file which will export your store slice, like so:

- store
|-messages
| |-messages.actions.ts
| |-messages.effects.ts
| |-messages.facade.ts
| |-messages.reducers.ts // <-- here, our feature creator slice will live
| |-messages.selectors.ts // <-- if no additional selectors needed, this can be removed
| |-messages.state.ts

Before we start! There’s one restriction, you need to be aware of.
You cannot used Feature Creators with state optional properties! The structure of your state initial object has to be static!

This is how our revamped messages.reducer.ts file should look like:

import { createFeature, createReducer, on } from '@ngrx/store';
import { messagesAdapter, messagesInitialState, MessagesState } from './messages.state';
import { addMessage, deleteMessage } from './messages.actions';

export const messagesFeature = createFeature({
name: 'messages',
reducer: createReducer(
messagesInitialState,
on(addMessage, (state: MessagesState, { message }) => messagesAdapter.addOne(message, state)),
on(deleteMessage, (state: MessagesState, { id }) => messagesAdapter.removeOne(id, state))
),
});

export const {
selectMessagesState,
selectIds,
selectEntities,
selectLoading,
} = messagesFeature;

Let’s analyze the code above, to understand the key differences. We declared our messagesFeature by calling the createFeature function provided by @ngrx/store and passing a configuration object with name and reducer as attributes.

As a result, Feature Creator provides you some default selectors. For every state property as well as feature selector itself!

You’re probably wondering what would be the names of automatically generated selectors. The rules are pretty simple:

  • feature selector name — [select][yourFeatureName][State]
  • selector name for each attribute — [select][stateAttributeName]

I’ve intentionally left both approaches: export const messagesFeature as well as destructured export of selectors from messagesFeature, as both are possible and can be used independently.

Now, we need to register our feature slice within our NgRx implementation for StandaloneComponents, therefore we need to make some changes to the main.ts file:

import { enableProdMode, isDevMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

import { environment } from './environments/environment';
import { AppComponent } from './app/app.component';
import { provideState, provideStore } from '@ngrx/store';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { messagesFeature } from './app/store/messages';
import { postsReducers } from './app/store/posts/posts.reducers';

if (environment.production) {
enableProdMode();
}

bootstrapApplication(AppComponent, {
providers: [
provideStore({ posts: postsReducers }),
provideState(messagesFeature), // <-- this is the place! :-)
provideStoreDevtools({
maxAge: 25, // Retains last 25 states
logOnly: !isDevMode(), // Restrict extension to log-only mode
autoPause: true, // Pauses recording actions and state changes when the extension window is not open
trace: false, // If set to true, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code
traceLimit: 75, // maximum stack trace frames to be stored (in case trace option was provided as true)
}),
],
}).catch(err => console.error(err));

I’ve intentionally shown the postReducers, which are not part of our current article topic (this feature store was made without Feature Creators). As I wanted to highlight, there’s no restriction to use one approach and only!

Although, in general, it is a good practice to use the same approach to make your code easier to read and understand, as we do not like to switch context too much!

Our messagesFeature has to be registered with provideState function from @ngrx/store library and we pass our feature as an argument.

If you’re not passing any non-Feature Creators-based feature stores, you still have to pass the provideStore() provider.

To summarize, NgRx provides some alternatives (not calling this a simplification intentionally) for feature store implementation. As this might seem to look like a slightly simpler approach, in the case of more complex feature stores it might not be as flexible, and readable as expected.

💡 Tip: If you find yourself using the same components repeatedly for different projects, consider using Bit to package, test, document, and publish them independently so you (or others) can import them in any project with a simple bit import your.username/your-component command.

Learn more:

Or maybe I am too biased, as I’ve been using a non-Feature Creators-based approach for too long?

Which side do you choose? The choice is yours!

Build composable Angular apps with reusable components, just like Lego

Bit is an open-source toolchain for the development of composable software.

With Bit, you can develop any piece of software — a modern web app, a UI component, a backend service or a CLI script — as an independent, reusable and composable unit of software. Share any component across your applications to make it easier to collaborate and faster to build.

Join the 100,000+ developers building composable software together.

Get started with these tutorials:

→ Micro-Frontends: Video // Guide

→ Code Sharing: Video // Guide

→ Modernization: Video // Guide

→ Monorepo: Video // Guide

→ Microservices: Video // Guide

→ Design System: Video // Guide

--

--

JavaScript/Typescript experience with biggest corporates and scalable software, with focus on reactive / performance programming approach / high code quality.