A few years ago, I published a tutorial on how to do polling with RxJs and Angular. The goal was to illustrate how to retrieve and render information that gets refreshed periodically.
With the latest iterations of Angular, we can simplify that approach a lot and implement a similar feature with less code, better performance, and no risk of memory leak!
And we’ll be using Signals, too!
First, let’s take a look at the 2020 version of my polling example:
@Injectable()
export class CurrencyService implements OnDestroy {private allCurrencies$: Observable;
private stopPolling = new Subject();
constructor(private http: HttpClient) {
this.allCurrencies$ = timer(1, 3000).pipe(
switchMap(() => http.get('http://localhost:8000/currencyInfo')),
retry(),
tap(console.log),
share(),
takeUntil(this.stopPolling)
);
}
getAllCurrencies(): Observable {
return this.allCurrencies$.pipe(
tap(() => console.log('data sent to subscriber'))
);
}
ngOnDestroy() {
this.stopPolling.next();
}
}
We have one Observable that comes from the HTTP client and a timer to repeat the request every 3 seconds. Then we use share() to turn that Observable into a multicast version so we support multiple subscribers. That way, we don’t make one request for each new call to .subscribe().
Finally, we use a subject that emits a value when the service is destroyed, so we stop polling and unsubscribe.
That’s a basic implementation, yet it’s complex enough to give some headaches.
A few years later, if we want to use a Signal instead of an Observable, we simplify our code quite a bit because:
- Signals are already “multicast,” so there is no need to use the share() operator.
- Signals automatically stop and free resources when all “subscribers” are destroyed, so there is no need to use a Subject to unsubscribe.
So here is the same service rewritten for Angular 18 and exposing a Signal instead of an Observable:
@Injectable({
providedIn: 'root'
})
export class CurrencyService {
readonly allCurrencies: Signal;constructor(private http: HttpClient) {
this.allCurrencies = toSignal(
timer(1, 3000).pipe(
switchMap(() =>
http.get('http://localhost:8000/currencyInfo')
),
retry(),
tap(console.log)
),
{ initialValue: [] }
);
}
}
We still rely on RxJs because of the HttpClient and handy features such as timer and retry. We use an empty array as the default value so there’s always something returned by our Signal (no need to handle null or undefined in our components).
This makes our component easy to read using modern Angular features such as the inject function.
Before with RxJs:
export class AppComponent {currencyInfo$: Observable;
constructor(public currencyService: CurrencyService) {
this.currencyInfo$ = currencyService.getAllCurrencies();
}
}
And now with Signals and inject() — one line of code only!
export class App {
exchangeRates = inject(CurrencyService).allCurrencies;
}
What about the template of the RxJS version circa 2020 with the async pipe and its directive syntax tricks to handle the auto-subscription and unsubscription:
Current exchange rates
{{info.currency}}: {{info.exchangeRate | number}}
And the updated Signal-based version with new control flow (zero standalone imports needed — no subscription nor unsubscription):
@for(info of exchangeRates(); track info.currency) {
{{info.currency}}: {{info.exchangeRate | number}}
}
Doesn’t our Angular code look a lot lighter, concise, and almost magical now?
You can access the code of my earlier example here on Stackblitz and the updated version with Signals here.
My name is Alain Chautard. I am a Google Developer Expert in Angular and a consultant and trainer at Angular Training, where I help web development teams learn and become comfortable with Angular.
If you need any help with web development, feel free to get in touch!
If you enjoyed this article, please clap for it or share it. Your help is always appreciated. You can also subscribe to my articles and my Daily Angular Newsletter to receive helpful daily tips.