Core

Media

The Media service provides a reactive API to track media query changes. It supports both responsive breakpoints and user preferences such as prefers-color-scheme. Internally, it uses the Angular CDK's MediaMatcher and Signals to expose reactive media state.

Overview

Responsive layouts and adaptive UI behavior often rely on media queries. Rather than scattering matchMedia logic throughout your components, this service centralizes and standardizes media query handling in a reactive way. It also automatically cleans up listeners and integrates with Angular’s platform detection.

Signal-Based Media Tracking
Creates a signal for each media query, letting you reactively respond to viewport or preference changes.
Automatic Cleanup
Media is provided in the root injector, so its listeners stay active for the lifetime of the application and are removed when the root injector is destroyed, not when the component that called match() is destroyed.
Browser-Only Listeners
Change listeners are only registered in the browser, so the service is safe to use during server rendering.

Provider Setup

Add provideMedia to the providers array of your application configuration:

import { provideMedia } from '@/app/core/media';

export const appConfig: ApplicationConfig = {
providers: [
provideMedia(),
// ...other providers
],
};

Usage

match() returns a Signal<boolean>, so assign it to a field and read it in the template:

@Component({
selector: 'app-layout',
templateUrl: './layout.html',
})
export class Layout {
private media = inject(Media);

private isMobile = this.media.match('(max-width: 767px)');
}
@if (isMobile()) {
<app-mobile-nav />
} @else {
<app-sidebar />
}

Use computed() to derive state from several queries. The service can be injected into services as well as components.

private isMobile = this.media.match('(max-width: 767px)');
private prefersReducedMotion = this.media.match(
'(prefers-reduced-motion: reduce)'
);

private animateSidebar = computed(
() => !this.isMobile() && !this.prefersReducedMotion()
);

How It Works

  • The match() method takes a media query string and returns a Signal<boolean> that tracks whether the query currently matches.
  • Calling match() again with the same query string returns the same signal, and only one listener is registered per query.
  • On the client, it sets up a listener for the media query and updates the signal when it changes.
  • On the server, the CDK's MediaMatcher does not evaluate media queries, so the signal is false for every query except all and no listener is registered. The server renders with that value and the browser resolves the real one, so markup that depends on a media signal can differ between the two.

Note: The service is made for a fixed set of queries, such as breakpoints and user preferences, and repeated calls with the same query share one listener no matter how many components use it. Prefer fixed query strings over ones generated at runtime from changing values, since each distinct string is kept for the lifetime of the application.