👋 Hey there!

I'm Josh Morony, and this website is a collection of my free content centered around creating modern Angular applications using the latest Angular features, and concepts like reactive and declarative code.

Tutorial hero
Lesson icon

Service with a Subject vs Service with a Signal in Angular

Originally published April 12, 2023 Time 6 mins

Perhaps the most widely used state management approach in Angular is a “service with a subject”. In almost any debate about state management libraries you will almost always see comments like “just use a service with a subject”. It might not actually always be the best approach, but there is a reason this approach is so popular and it’s because it is simple and it works.

This article is adapted from the following video:

Understanding ‘Service with a Subject’

If you need to share some state among multiple components, you can create a service and add a subject to that service to hold the state.

Injectables with a new behavior subject

You will generally have some method in the service to update that subject with new data, and then any component that wants to use the data in that subject can do so either by directly accessing the subject, or to make things a bit more safe we can expose the subject as an observable instead. This means code from outside of this service can access the values, but it has no power to modify those values directly by having access to that subject.

Updating new subject

But now signals are coming in like a wrecking ball and challenging all of our beloved patterns.

I am almost certain this pattern will remain popular, and the signal-ified version of it is just going to make things a little bit easier in some ways… and perhaps harder in others.

Enter the ‘Service with a Signal’

I’ve taken the service with a subject idea and created the equivalent set up with signals. These end up being quite similar.

Perhaps an interesting aspect to this signals version is that rather than just having other components access the settable signal directly, we provide a computed version of that signal which will give components the ability to read the signal, but not modify it. This is the same idea as returning an observable of a BehaviorSubject rather than the BehaviorSubject directly.

Updating new subject

So, what benefit do signals offer here? Mostly the same benefits they usually offer - we don’t have to deal with observables and subscriptions here, we can just access signal values directly, and we don’t need to understand the more complex aspects and gotchas of RxJS. I talked about the benefits of signals over a subject in one of my previous videos.

A Fair Comparison: Handling Asynchronous Data

This is clearly a very simple example. It wouldn’t be fair to just say signals are better for services.

So, let’s take a look at a situation involving async that I often run into. Right now, this service allows us to start from a blank state and add or remove data. But, often I want to sync this data either to local storage or perhaps some external API. This means that I want to initially seed this service with data from the API, and every time a change is made I want to replicate that change back to the API to make sure it is saved.

API/Database

Now, I could just manually subscribe to a request to load the data from the API and next the subject in this service to initialise the data. This leads to a simpler mental model, but doesn’t really make use of RxJS’s async power.

nexting data

Instead, I can bust out some RxJS operators and do something like this instead.

RxJS operator code

Now rather than having a subject that contains all of our values, we instead have a subject that just emits when we want to add or remove a value - in this case names of employees.

This employees$ observable will listen to all of the emissions from this subject, and collect them into an array using the scan operator.

But, to get our initial data we first call the .load method from our API service, and then we pass that data when we are switching to our subject that emits to add or remove a value, and use it as our initial value for the scan. That means our scan will initially be an array of the initial set of data, and then every time our subject emits we will either add or remove names from that array.

To save the data back to the API every time it changes in the app, I’ve added this tap operator which allows me to run a side effect whenever this stream emits.

RxJS operator code

This is probably a reasonably fair example of RxJS - there are some non obvious concepts here that aren’t easy to learn, but this also provides a lot of power and flexibility and can be completely reactive and declarative.

Check out this video if you want more info about what that means exactly.

Now, here is what the signals version might look like:

signals version

We have this init effect that triggers loadEmployees. The initialEmployees just uses async await to load the data from the service and then we update the employees signal. To save the data every time it changes, we have this save effect.

This signals example looks quite a bit simpler, and it is in a sense, but there is also some awkwardness here. This code is much more imperative. We need to do things like setting this hasLoaded flag to make sure we don’t save our data back to the API before any data has loaded, and to actually understand what values employees will hold and how it behaves we need to search around in multiple places in this service.

With a declarative approach, like with RxJS, we only need to look in one place to understand what something is.

signals version

Blending Signals and RxJS

An important thing to remember is that we can use signals and RxJS together, so if you wanted to go with the RxJS approach you can just wrap your employees$ observable with toSignal or whatever we end up getting in the API to convert back to a signal. You get all of the RxJS goodness and power, but you still have a signal in end that you can use to access the value directly without any subscriptions.