Skip to content

useDocumentVisibility

Creates a signal that tracks whether the document/tab is visible or hidden. The signal updates when the user switches tabs or minimizes the window.

MDN Reference: Page Visibility API

When to Use

Use useDocumentVisibility when you want to pause or resume activity based on tab focus — for example, pausing animations, stopping polling, or pausing video playback when the user switches away.

Usage

typescript
import { useDocumentVisibility } from 'ng-reactive-utils';

@Component({
  template: `<h1>Tab currently visible: {{ isVisible() }}</h1>`,
})
class ExampleComponent {
  isVisible = useDocumentVisibility();
}

Pause Polling When Hidden

typescript
import { useDocumentVisibility } from 'ng-reactive-utils';

@Component({ template: `...` })
class DashboardComponent {
  isVisible = useDocumentVisibility();

  constructor() {
    effect(() => {
      if (this.isVisible()) {
        this.startPolling();
      } else {
        this.stopPolling();
      }
    });
  }
}

Parameters

This composable takes no parameters.

Returns

Signal<boolean> — A readonly signal that is true when the page is visible, false when hidden (tab is backgrounded or window is minimized). Defaults to true on the server (SSR).

Notes

  • Returned signal is readonly to prevent direct manipulation
  • Uses createSharedComposable internally so there is only one shared instance at a time; event listeners are torn down automatically when no more consumers exist
  • SSR safe: defaults to true on the server where document is not available

Source

ts
import { signal, inject, PLATFORM_ID } from '@angular/core';
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import { createSharedComposable } from '../../../utils/create-shared-composable/create-shared-composable';

/*
 * Creates a signal that tracks whether the document/tab is visible or hidden.
 * The signal updates when the user switches tabs or minimizes the window.
 *
 * On the server, returns `true` (visible) by default and updates to actual value once hydrated on the client.
 *
 * Example:
 *
 * const isVisible = useDocumentVisibility();
 *
 * // Use in template
 * @if (isVisible()) {
 *   <div>Tab is visible</div>
 * } @else {
 *   <div>Tab is hidden</div>
 * }
 */
export const useDocumentVisibility = createSharedComposable(() => {
  const document = inject(DOCUMENT);
  const platformId = inject(PLATFORM_ID);
  const isBrowser = isPlatformBrowser(platformId);

  // On server, default to visible (true). On client, use actual document.hidden state
  const getInitialVisibility = () => (isBrowser ? !document.hidden : true);

  const visibilitySignal = signal<boolean>(getInitialVisibility());

  const handleVisibilityChange = () => visibilitySignal.set(!document.hidden);

  // Only set up event listeners in the browser.
  // visibilitychange fires on document (not window) per the Page Visibility API spec.
  if (isBrowser) {
    document.addEventListener('visibilitychange', handleVisibilityChange);
  }

  return {
    value: visibilitySignal.asReadonly(),
    cleanup: () => {
      if (isBrowser) {
        document.removeEventListener('visibilitychange', handleVisibilityChange);
      }
    },
  };
});

Released under the MIT License.