Benefits of using NgRx Signal State/Store over Signals

Marek Panti
Multitude IT Labs
Published in
4 min readMar 9, 2024

In this article we will go through some of the best practices when using NgRx. We will go through the naming, what to think about when working with NgRx, the difference between classic state management and NgRx Signals.

Before we dive in, I would like to explain my way of explaining: the goal of my article is to show very briefly the point, so I’d try not to talk much about details -> as it is a complex topic, and I am concentrating on showing the differences between having your data in a pure Signal vs. Signal Store vs. Signal State and explaining how you should work with the data overall in your Angular project.

How to store the store (in the store)?

Let’s think about the Object and our codebase in general for a while and what we want to achieve. All we want is reusability, clean code, pure functions.
1. Let’s imagine that we have a book store, and each book can have statuses: IN_STOCK, OUT_OF_STOCK, LAST_PIECES, and ON_SALE. The statuses can be variable, and the user can filter based on the statuses.
2. Then we can have various categories: CLASSIC_LITERATURE, SCIENCE_FICTION, MOTIVATION … etc
3. List of our books
4. List of authors

DONT'S:

{
"books": [
{
"name": "Harry Potter",
"author": {
"name": "J. K. Rowling",
"description": "some description",
"books": [{...listOfBooks}]
},
"category": {
"name": "Fantasy",
"description": "Fantasy description"
},
"status": "ON_SALE"
}
]
}

I was often helping a team with such a weird state because they tried hard to use a store and put everything there so nested that when you needed Information X, it was nested in XYZ, but at the same time, it had its separate branch within the store.

DO'S

This is an example of raw data before I would put it in the store. It is good practice to just write down the data before creating components. It helps you organize.

// Authors
const authors = [{
"author_name": "J. K. Rowling",
"author_id": 1,
"author_birth": "date",
"author_books": [123, 124, 125]
}]

// Categories
const categories = [{
"name": "FANTASY",
"id": 1
"description": "description"
},
{
"name": "CLASSIC_LITERATURE",
"id": 2
"description": "description"
}]

// Statuses
const statuses = [{
"status_name": "ON_SALE"
"status_id": 1,
},
{
"status_name": "OUT_OF_STOCK"
"status_id": 2,
}]

// Books
const books = [{
"book_title": "Harry Potter",
"book_author_id": 1,
"book_category_id": 1,
"book_status_id": 1
}]

// Our metadata -> user interaction information
const metaData = {
"selected_book_id": 1,
"loading_books": false,
"selected_author": null,
"loading_authors": true
}

What have we achieved so far?
We have a good structure, similar to a database. Based on that structure, we can do almost everything:

  • Easily perform any filtering or sorting.
  • Connect any book with an author or an author with a book.
  • Filter books based on Author, Status, Category, and our metadata easily.

Now, once we have clarified how the data should look, we can start deciding whether we need state management or can rely on signals or BehaviorSubjects.

In what case can we rely on pure signals, or actually, when I saw how the Signal State looks, I would recommend the following:

type BooksFeature = { 
books: Book[];
authors: Author[];
statuses: Status[];
categories: Category[];
metaData: MetaData;
};

const booksFeatureState = signalState<BooksFeature>({
authors: [{
"author_name": "J. K. Rowling",
"author_id": 1,
"author_birth": "date",
"author_books": [123, 124, 125]
}]
categories:[{
"name": "FANTASY",
"id": 1
"description": "description"
},
{
"name": "CLASSIC_LITERATURE",
"id": 2
"description": "description"
}]
statuses: [{
"status_name": "ON_SALE"
"status_id": 1,
},
{
"status_name": "OUT_OF_STOCK"
"status_id": 2,
}]
books:[{
"book_title": "Harry Potter",
"book_author_id": 1,
"book_category_id": 1,
"book_status_id": 1
"id": 1
}]
metaData: {
"selected_book_id": 1,
"loading_books": false,
"selected_author": null,
"loading_authors": true
}
});

With such a state we can playfully obtain a lot of combination and the data, for example let's retreive a selected_book_id

public getSelectedBook(): Book {
return this.booksFeatureState.books().filter(book => {
book.id === this.booksFeatureState.metaData.selected_book_id()
}[];
}

Do not forget to place these methods in a facade service; then, you can just call the method from the component. Also, you can find the full documentation for signal state here: https://ngrx.io/guide/signals/signal-state.

Also, it is completely up to you whether you want to use signal state or just signals. For very simple things, I would go with pure signals. However, if you want to have some structure and if you are cooperating on a project with other developers, then I would recommend using signal state.

When to use signal store?

Again, we need to understand that a signal store will bring some more complexity and a little bit of boilerplate to our code. The rule of thumb is when you have a simple to mid-size project and you have up to 4–5 developers on a project, you can be safe with signal state or pure signals. But again, nothing can go wrong if you decide to use a signal store — the signal store is very well designed, and it is not as complex, and the boilerplate is far from the NgRx Store.

I will write another article just for the signal store; for now, you can check the official example: https://ngrx.io/guide/signals/signal-store.

Keep in mind that for the signal store, you can create custom methods, state properties, and queries; it works really smoothly. And with Angular 18, it will be production-ready.

In conclusion, we saw some bad practices and refactored them to serve our purpose. We saw that whatever tool we use, firstly we have to have our basics done properly; we need to think and plan. Then, once we know what and how, only then can we decide on the tool.

--

--

Marek Panti
Multitude IT Labs

I am a web developer and UX designer. I love creativity and creating modern, nice and clean applications. www.app-masons.com