Angular Signals in Practice

Introduction

Signals have been around in Angular for a while now. But if you are like me, you might still be unsure when or why to use them. I understand how I declare them, update them, output their values in the template, etc. But I have been unsure sometimes when to use them. However, recently I ran into a situation where I refactored a piece of code from using a BehaviorSubject to using signals. In this post, I’ll show my previous implementation using RxJS, and then the refactored version with Signals.

Overview of the Situation

Before we jump in to the code, let’s review what this use case is. I work for an insurance company, and we want to make sure that when users are idle on our site for a certain amount of time, they are automatically logged out of the site. We don’t want them to accidentally stay logged in and give access to someone that shouldn’t have access to the site. We’re using a library called ng-idle to determine if the person is idle or not.

In the application, we initialize the idle watch, and then use Angular’s reactivity to show a modal several seconds before they are logged out. If they become active again, the modal is hidden. If they don’t, they are logged out. This example will look at one example where the modal is shown due to the value in a BehaviorSubject vs the value of a signal.

What this blog post is not is a decree about which format is better. They both work well and are inherently reactive. But it does give you an example of when you might use Signals instead of RxJS and a clear example of the difference in the code in the two situations.

BehaviorSubject

In our initial version of the idle user feature, we used a BehaviorSubject to contain how much time was left remaining before the user was logged out. ng-idle provides an EventEmitter, onIdleTimeoutWarning, that fires and emits a number. That number is the amount of time remaining before the user has been idle too long. Our component looks like this when handling that value:

// app.component.ts

private idleTimeRemainingBs: BehaviorSubject<number> = new BehaviorSubject<number>();
public idleTimeRemaining$: Observable<string> = this.idleTimeRemainingBs.asObservable().pipe(
filter((val) => !!val),
map((time: number) => {
if (time / 60 > 1) {
return `${Math.ceil(time / 60)} minutes`;
}

return `${time} seconds`;
})
)

ngOnInit() {
this._idle.onIdleStart.pipe(
untilDestroyed(),
tap(() => {
this.showIdleModal = true;
})
).subscribe();

this._idle.onIdleEnd.pipe(
untilDestroyed(),
tap(() => {
this.showIdleModal = false;
})
).subscribe();

this._idle.onIdleTimeoutWarning.pipe(
untilDestroyed(),
tap((time: number) => {
this.idleTimeRemainingBs.next(time);
})
).subscribe()
}

Let’s break this code down really quick. The first line declares and initializes the BehaviorSubject. It’s kept private and not able to be used in the template. Then an observable is created that either returns the number of minutes or the number of seconds remaining until the timeout is reached. This observable is what is used in the template.

In the ngOnInit lifecycle method, we use three methods from the Idle service. The first is when the Idle monitoring starts, and the second is when it ends. When either event fires, we show or hide the modal that tells the user how much time they have left. The final method is onIdleTimeoutWarning. When it fires, we take the number emitted and call .nex() on our BehaviorSubject, which then automatically updates the observable.

When the modal is visible, the idleTimeRemaining$ observable is subscribed to in the template with an async pipe, and the amount of time left before logout is shown on the screen. It’s pretty straightforward, with some definite room for improvement, but it also does get the job done. Now let’s look at the same functionality implemented with Signals.

Signal

There is some overlap with the code in this example, so I’m only going to show the differences. The onIdleStart and onIdleEnd pieces are the same. The difference is in the storing and displaying of the amount of time left before being logged out.

// app.component.ts

private idleTimeRemainingSignal = signal<number | null>(null);
public idleTimeRemaining = computed(() => {
const time = this.idleTimeRemainingSignal();

if (time === null) {
return ”;
}

if (time / 60 > 1) {
return `${Math.ceil(time / 60)} minutes`;
}

return return `${time} seconds`;
})

ngOnInit() {
this._idle.onIdleTimeoutWarning.pipe(
untilDestroyed(),
tap((time: number) => {
this.idleTimeRemainingSignal.set(time);
})
).subscribe()
}

Not a whole lot different, really. The only difference is that we are using a writable signal to keep track of how much time is remaining, and a computed signal to display that time to the user in the modal. Basically everything else is the same, and just like in the other situation, there is room for improvement.

So What?

So, what’s the point of this blog post and this refactor? My whole purpose really is to show an example of a time when you could use signals versus using RxJS, and how you’d go about doing that. They have the same result. It’s just two ways of doing things. But when you’re first starting to use signals, you might find yourself confused about when you’d use them. If you’re like me and have been using RxJS for a long time and worked hard to learn it at a deep level, you might have a hard time understanding what to do here. I hope this example helps.

Conclusion

In the end, it really doesn’t matter what you use. However, Signals are the way of the future and the newer way of writing Angular. LinkedSignals, Signal Inputs, and using the Resource API are all examples of how Signals are becoming more and more a part of the framework. Understanding how to use them and when to use them is vital as you move forward with Angular.

Angular Signals in Practice was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *