The Frontend Ecosystem lately
The world of frontend development is always buzzing with new ideas, tools, and frameworks. It’s like a giant playground where everyone’s trying out cool new toys. But slowly over the past years, something surprising has been happening: the authors behind these frameworks are starting to agree on some key ideas. Shocking, right?
For example many agree on:
- — Server-side rendering (SSR) is a must
Many frameworks keep pushing the boundaries of what is possible with SSR. And since it boosts performance and SEO, it’s become the “must-have” feature.
- — Components over templating
It’s all about components now! Forget templating, component based architecture is the new standard. Almost everyone’s on board. (Okay, maybe not everyone, but close enough. )
- — Web Components Are… Not Fun?
Let’s just say web components haven’t won the popularity contest. Most devs agree they’re tricky to use, so you’d better have a pretty convincing reason to pick them over regular frameworks.
- — Signals everywhere
Signals ha’ve taken the community by storm, and for good reason! Like I mentioned in a previous post signals are becoming the new “must-have feature”. Ryan Carniato popularized the idea of them through SolidJS. Soon after many frameworks followed like Svelte5, Angular, Vue, Preact, Qwik.
Why do we need signals?
The short answer is: we don’t. Just like we don’t need a Virtual DOM, component-based architectures, or even an SPA framework to deliver a better UX. What signals offer is a fresh perspective: simplicity, readability, ease of use, and direct dependency tracking. They allow you to reduce complexity and manage reactivity in a more intuitive, straightforward way, and all of this natively!
Understanding signals.
Using the actual definition of signals from Angular in particular (although the underlying concept should be the same for the rest) we get:
The key takeaways from the above is that signals:
- — Help the framework to optimize rendering updates.
Signals ensure that whenever you access their values, you always get the latest values. Moreover computed
signals
are lazily evaluated. This also guarantees glitch-free UI synchronization.
- — Granularly track our state.
Signals operate at a fine-grained level, allowing you for more precise control over updates and reactivity.
All signals operations are synchronous!
Updating a signal happens synchronously, and any computed
signal dependent on it will immediately reflect the change.
Watchers are also notified synchronously as well.
Leveraging signals for asynchronous operations.
Since we’ve established that all signal operations are synchronous, how can we leverage their benefits for asynchronous operations? There are some approaches and some best practices when using signals for that.
So we will see a small demo of an app and try to replicate it with different approaches and explain why some of them preferable and some are not.
Demo App
Our objective is to implement a lightweight asynchronous feature: fetching the astronomy picture of the day from NASA’s API for a specified date. When the date changes, it should trigger a new fetch, and any ongoing requests should be canceled to handle updates efficiently. NASA’s endpoint
Rxjs focused approach
First, let’s explore a more traditional RxJS-focused approach. By using a Subject
combined with switchMap
, we can initiate and cancel ongoing requests efficiently.
There couple things we can note here. Not only we take advantage of powerful RxJS operators but the key benefit here is that we maintain a unidirectional state pattern by seting our state at the end of our asynchronous flow. This means we have a signle state of truth that always remains immutable and only then we ensure UI consistency as well by leveraging the power of signals to seamlessly synchronize the UI.
However we have a small drawback here, we need to break our declarative paradigm since we need to subscribe
, imperativly set our data, and
handle the subscription itself.
Naive signals approach
In this case, our date signal triggers the effect, and inside the effect, we store the data using again the pictureOfTheDay signal.
But our example above lacks the functionality of a switchMap
like before.
Also we are setting a signal within an effect
block.
Seting a signal within an effect.
Seting a signal within an effect can create couple issues. Depending on the implementation it can create “glitches” since it is scheduled by the framework and the timing of some operations may not be so intuitive at first (will elaborate later), also it could produce an infinite loop:
If you cannot avoid the above pattern for whatever reason you could utilize the untracked
API provided
by the Angular team.
And while we could utilize untracked
to make our previous example more readable and have more depedancy control,
we would still lack the switchMap
behavior.
Use of third party libs
We bridge this gap between our signals and Rxjs we could utilize third party libraries like: ngxtension
.
From that we could use derivedAsync
API which brings the best of both worlds:
Here derivedAsync
makes the asynchronous call, provides us with back a Signal
type, and it also manages to use the
Rxjs operators. How does it achieve all of this?
Well if we check parts of its source code:
Source code of derivedAsync
We notice the following:
-
— Uses the
effect
block to schedule the asynchronous operation. -
— Forces the user to run the
effect
inside an injector context.
This is because the effect
uses the DestroyRef
service to unsubcribe
.
- — Makes use of the
untracked
API to ensure to avoid an infinite loop. - — Utilizes a Subject instead of a signal.
This is happening for two reasons. First of all it uses pipe()
into the Rxjs operators,
and most imporantly (although not the case here) its to ensure that all the events will be eagerly propageted to the system.
Native solution.
Thankfully now with Angular19 we do not need to use any external library to achieve this behavior not even Rxjs! Especially for newcomers I think this is a great selling point.
Resources object.
With Angular19 we also get the resources()
API.
Which although still experimental its proven very promising.
With this approach all we need to do is pass a callback to the loader
property and also give it the request
property which
basically tells the resource object to repond to changes from the provided signal.
Not only that but it will also cancel any on-going HTTP calls using an AbortSignal
, so we can have the same functionality we had with a switchMap
operator!
Creating a resource object will provide us with other usefull signals as well:
Linking signals.
Lets try now to expand our app by adding a gallery for favorite days. With this feature, you can save specific days to local storage and later view them, along with their descriptions, without needing to fetch them again.
So our feature is working great, except from the fact that when we update our date()
signal after we have selected a picture from our
gallery doesn’t seem to show our newly fetched picture.
If we check our implementation it makes sense since in our template:
Well one obvious solution to our issue it would be to maybe reset our selectedPicture
when our date
changes.
And while the above works for our usecase, its a bad idea because like we mentioned already effect
’s are scheduled
which means that we have sometime between our date
signal being set and our actual effect
being scheduled to run.
This can create glitches to the user, if not careful enough.
What would you do instead is this:
we could shift our perspective and initialize a completely new signal directly when we receive a new value. This way, we eliminate the need for resetting and ensure the signal is updated reactively and consistently.
Alternativly you could use a new experimental API called linkedSignal
:
✨ TLDR ✨
In conclusion its worth keeping in mind the following:
- — ✅ Use signals if it alleviates complexity.
- — ✅ Use signals for UI synchronization.
- — ❌ Avoid signals for very complex asynchronous operations. (case dependant ofc)
- — 👉 Signals when accessed are always providing the latest value.
- — 👉
computed
signals are lazily evaluated. - — 👉 Effects are primitive wrappers of
watchers
signals. - — 👉 Effects are always asynchronous and framework dependant.
- — ❌ Do NOT use
effect
to synchronize the state or other signals.