With most Angular applications it comes to the need to apply changes at time intervals.
Typical patterns are
- updating data from the BE
- updating the UI in details
In many cases this is triggered by Observables like
interval(3 * 60 * 1000).pipe(
startWith(0),
or
timer(10, 5 * 60 * 1000).pipe(
Issue
Issue is, that this timed events occur - even if the window is not visible in the browser. Further processing occupies resources limited,even if the window is not visible.
Solution
To avoid occupation of restricted resources, such time-based events should be filtered out. As close as possible to the source.
For this purpose, the visibilitychange
-event of the Browser document
may be intercepted.
It reports visibility change events.
WindowVisibilityService
The usual Angular approach is, to handle this in a service. We are using our WindowVisibilityService
for this purpose.
It is defined as
import { Injectable } from '@angular/core';
import { BehaviorSubject, fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
export type VisibilityStateType = 'visible' | 'hidden' | 'prerender' | 'unloaded';
/**
* Service to provide window visibility information
* Provides encapsulation of Page Visibility API
*/
@Injectable({
providedIn: 'root'
})
export class WindowVisibilityService {
private visibilityStateSubject: BehaviorSubject<VisibilityStateType> = new BehaviorSubject<VisibilityStateType>(document.visibilityState);
public visibilityState$ = this.visibilityStateSubject.asObservable();
constructor() {
fromEvent(document, 'visibilitychange', { passive: true }).pipe(
debounceTime(20),
distinctUntilChanged(),
tap(() => {
this.visibilityStateSubject.next(document.visibilityState);
}),
).subscribe();
}
visibilityState(): VisibilityStateType {
return document.visibilityState;
}
}
An interesting point with this service is, that we get another trigger for updates apart from timed events - if the window gets active again.
Combined usage
With the aim to update information on time intervals - or when the browser window becomes visible again, we commonly merge time based events and window visibility events like this:
merge(this.windowVisibilityService.visibilityState$, timer(10, 5 * 60 * 1000)).pipe(
filter(() => this.windowVisibilityService.visibilityState() === 'visible'),
Thanks for reading.