NgRx v18 Signals (Part 1): Signal State
Hooman Pouyanasab
Senior Frontend Engineer | Team Lead | Angular Lead Developer | Crafting High-Impact, Scalable and Innovative Frontend Solutions | MEAN & MERN Stacks
?? Angular has been evolving quite a lot recently and with the arrival of signal-based components it will be completely a new framework, but of cource it would still be fully backward compatible. one of the major players in this jurney is gonna be signals for sure. In this article, I’m going to provide you with a quick and simple guide on how to get started with NgRx’s new Signal State (a new way of state management with no redux pattern complexity or boilerplates). we will learn how to simply set it up in your Angular apps. By the end of this guide, you’ll be ready to leverage this powerful tool and feel comfortable using it.
?? ngrx/signal is in developer preview as of now
?? So Signals, Here We Go Again
Signals were introduced in Angular 16 and became stable in Angular 17. We all know that They represent a new approach to introducing reactivity in Angular applications. Before signals, Angular developers primarily relied on data streams such as Observables, Subjects, BehaviorSubjects, and their integration with Angular services (singleton classes that maintain the state of various features in your app).
Handling reactivity and state management using these tools typically involved RxJS, a standard library for reactive programming. RxJS provides operators to pipe and modify the inner data of Observables to produce the desired output. However, many developers found RxJS complex and challenging to master (i mean at least if you valued programming the declarative way you may found it useful as i do)
not to forget that For enterprise-level state management, we also have NgRx (Redux pattern and component store)
??NgRx = Angular + Redux + RxJS (Reactive extensions JavaScript)
Despite its benefits, NgRx came with significant drawbacks:
??Another tool for reactivity in Angular that i didn't mention was getter and setter accessors, which kept the UI in sync with changes in particular entities. Signals leverage a similar method for handling reactivity under the hood. Here’s a brief overview of how signals work
?? I'll dive deep into how signals work under the hood in another talk
the fact is signals use the similar method for handling reactivity under the hood but generally The Signal Way consists of three concepts:
Signals take advantage of the getter and setter pattern to notify subscribers automatically when their value changes. When a signal’s value changes, any dependent computations or components are automatically re-evaluated or re-rendered.
While there are way more technical details to explore, hope this description provided a simple and concise introduction to signals.
Now, let’s dive into the @ngrx/signals library, which helps manage more complex state transitions using signals.
?? Setting Up @ngrx/signals in Your Angular App
To get started, add the @ngrx/signals library to your app:
npm install @ngrx/signals
Alternatively, you can use ng add to add it via your package manager of choice:
ng add @ngrx/signals@latest
?? Next, decide if you want to use SignalStore or SignalState to manage your application states. Your choice will Surely depend on the complexity of your app
its up to you to decide if your app state transitions is complex enough to go all out with SignalStore or SignalState just does the trick for you, here are some comparisons for you to decide easier:
Signal State ??
Signal Store ??
?? We Will Delve deeply Into Signal Store in Part 2 and see some real creative uses with resolvers and angular 18.1 new introduced features but in this article we'll focus on signal state
just know for now that it introduces fully-featured tools for more extensive state management:
?? Signal State
?? Imports
First, import the necessary items from the NgRx library:
import { signalState, patchState, DeepSignal } from '@ngrx/signals';
?? Define Your State:
Create an interface/type for your state:
?? The Signal State Type must be a record/object otherwise you'll get an Error
type Task = {
status: "active" | "inactive" ,
detail: {
count: number,
isComplete: boolean
}
}
Provide an Initial State:
Define the signalstate initial value :
const initialTaskState = { status: "inactive", detail: { count: 0, isComplete: false } }
? Create Your State
SignalState is the main utility function to create your read only state and just like signals is initiated with () to accept an inital state value
taskState = signalState<Task>(initialTaskState);
signalstate ?? regular signals:
?? type DeepSignal
is a new type used in Signal State and Signal Store for nested properties
first let me introduce you to a problem we had with signals till now:
?? A Signal might contain a large, nested object, where i as a consumer only require some specific parts of it. i don't want to create these slices manually.
?? In both SignalState and SignalStore,Instead of exposing the entire state as a single signal each property of the state (whether it’s an object or record) automatically gets its own corresponding signal. This also applies to nested state properties. For instance, the deeply nested object within taskState initially appears as a DeepSignal!
?? DeepSignal doesn’t include set and update methods. Instead, it offers a more convenient alternative: the patchState function. With patchState, you only need to specify the values you wish to modify. This means you don’t have to clone the entire value as you would with update, nor do you need to pass the entire value as with set.
??So The above TaskState instance will contain the following properties:
this means that you can use any property seperatly as a signal in your template ??
for better performance deeply nested signals are generated lazily and initialized ONLY upon first access. ??
?? Ways to Update Your State
patchState ??
a function which allows you to update a piece of your state, it accepts 2 arguments
?? but what does sequence partial state mean ?
let's say you can update in 3 ways and the third one is the answer
?? you can also use a custom function to update your state
Custom State Updaters
A Comparison: ??
?? Using SignalState As an Injectable Dependency
?? Signal State can be provided locally and globally. By default, a Signal State is not registered with any injectors and must be included in a providers array at the component, route, or root level before injection.
let's define TaskStore and use it in our component
TaskStore Class
Component Class
?? How to load async data in our store?
?? via RxMethod: Integrating RxJS with SignalState
first lets import what we need for this step
import { tapResponse } from '@ngrx/operators';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
Whats RxMethod ??
RxMethod is a standalone factory function designed for managing side effects by utilizing RxJS APIs
what does that actully mean though ?! ??
?? Think of it as a function that's just the entry point. it accepts a series of RxJS operators, often referred to as a “pipe”. This chain of operators (from now on i refer to it as reactive chain) processes the observable returned by a service method inside (in our case TaskService => getTask method) see below:
?? if you look carefully you see i have provided a type: RxMethod<number>
?? this is because eventually our RxMethod will return a reactive method which may or may not have inputs (in the examples above loginDoubleNumber and getTask both accept an input with type number) when using RxMethod<T> the T is the type of input you send to the reactive method when it's called
to put it simple the input which is accepted by reactive method can be typed by providing a type to the rxMethod function.
?? The reactive method can accept a static value, signal, or observable as an input argument with type T
so in the example above what are we allowed to pass down as input to the getTask reactive method ?
?? correct, Signal<number> or Observable<number> or just a number (static)
whats are the difference you ask ?
here's an example with signal input to illustrate that a bit more:
?? now's your time, take a look at the example below and guess what it does:
tapResponse is just a simple safe operator from @ngrx/operators (we imported earlier) that ensures we implement error handling so that upon errors the entire chain of operators and finaly our reactive method is still working)
in a store we have defined a rective method called deleteArticle which accepts an input with type string (Article Id) and pass it down to reactive chain and finaly our service uses the id to delete the article through our api and finaly as side effect we:
?? now lets complete our primary example (TaskStore) but this time with RxMethod
same as before:
?? now we need to provide a Ractive Method in our TaskStore and then give a chain of RxJs operators as an input. these operators are in chrage of:
?? they do that in 3 steps in our pipe (reactive chain):
??remember when using RxMethod<T> the T is the type of input you send to the reactive method when calling, so here as you can see we set it as void meaning that there will be no argument for the reactive method since it's getAll() method
??♀? there are still a lot to explore about RxMethod but thats how far we go in this article
Use Store In Your Component
now you can simply provide the TaskStore in any component (but preferably the Parent Components not the presentationals) and by injecting it you will have access to both the state and the Reactive Methodes for managing the side effects
?? This new realm of signals does not end here. This article was intended to provide a simple guide to getting started with these new reactive tools. By following this guide, you should now have a solid understanding of how to begin using NgRx’s Signal State in your Angular applications.
In the next part of this series, we’ll delve deeper into SignalStore and explore its creative uses with resolvers and Angular 18’s new features, along with some interesting use cases.
I hope this guide has helped you catch up and gain a clear understanding of this new subject. I appreciate any corrections or comments that could improve this article.
Stay tuned for our next discussion!
??
awesome