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.