Media Queries with RxJS

Media Queries | Practical examples with RxJS

Authors
Gary Großgarten Gary Großgarten
Published at

tl;dr

It's super easy to handle Media Queries programmatically with RxJS! 🤗

Introduction

Media Queries in CSS

Media Queries are essential tools when building responsive layouts on the web. They are commonly used to hide / show / alter parts of the UI depending on the viewport dimensions or to switch between themes based on user preferences (e.g. Darkmode 🌙).

In CSS Media Queries are used like so.

@media (max-width: 767px) {
  /* apply styles */
}

Although this is already pretty great, we sometimes want to handle the state of a media query programmatically. For example, preventing the render of some components or dom elements on certain viewport sizes instead of just hiding things with display: none could lead to a better performance and less network requests to your server.

Media Queries in JavaScript

The vanilla javascript way of implementing such a functionality would be to use the window's matchMedia function. The function takes a string query and returns a MediaQueryList that can be used to get the current result of the query and listen to changes of the media query.

const mediaQueryList = window.matchMedia(`(min-width: 767px)`);

console.log(mediaQueryList.matches); // true or false

mediaQueryList.addEventListener('change', (event) =>
  console.log(event.matches) // true or false
);

// don't forget to remove the event listener ;)

Media Queries with RxJS

As an Angular developer, I make heavy use of RxJS in my applications. To neatly integrate Media Queries in my workflow I came up with the media Observable.

import { fromEvent, Observable } from 'rxjs';
import { startWith, map } from 'rxjs/operators';

export function media(query: string): Observable<boolean> {
  const mediaQuery = window.matchMedia(query);
  return fromEvent<MediaQueryList>(mediaQuery, 'change').pipe(
    startWith(mediaQuery),
    map((list: MediaQueryList) => list.matches)
  );
}

// Usage
media('(max-width: 767px)').subscribe((matches) =>
  console.log(matches) // true or false
);

We use RxJS fromEvent Observable creation function to listen for all changes to the MediaQueryList. To get the inital MediaQueryList, we use the startWith operator. The MediaQueryList is then mapped to the actual result of the query by using the matches function.

The media function returns a Observable<boolean> stream which can be used to subscribe to the current state and future changes of the media query.

Demos

The demos were built in an Angular workspace. As you can see, I make use of Angular's async pipe to subscribe to the media Observables.

Breakpoints demo

In this demo we use the media Observable to track the default Tailwind CSS screen breakpoints. Depending on the viewport dimensions certain elements are hidden using *ngIf. Resize your browser window to see the breakpoints change.

import { Component, HostBinding } from '@angular/core';
import { media } from './media';

@Component({
  selector: 'demo-breakpoints',
  templateUrl: 'breakpoints.component.ts',
})
export class BreakPointsComponent {

  sm$ = media(`(max-width: 767px)`);
  md$ = media(`(min-width: 768px) and (max-width: 1023px)`);
  lg$ = media(`(min-width: 1024px) and (max-width: 1279px)`);
  xl$ = media(`(min-width: 1280px) and (max-width: 1535px)`);
  xl2$ = media(`(min-width: 1536px)`);

}
<div *ngIf="sm$ | async">sm</div>
<div *ngIf="md$ | async">md</div>
<div *ngIf="lg$ | async">lg</div>
<div *ngIf="xl$ | async">xl</div>
<div *ngIf="xl2$ | async">2xl</div>

Device / Browser preferences demo

This demo watches device / browser preferences and the viewport orientation.

import { Component, HostBinding } from '@angular/core';
import { media } from './media';

@Component({
  selector: 'demo-preferences',
  templateUrl:'preferences.component.html',
})
export class PreferencesComponent {
  @HostBinding('class') class = 'block relative space-y-4';

  prefersLight$ = media('(prefers-color-scheme: light)');
  prefersDark$ = media('(prefers-color-scheme: dark)');
  prefersReducedMotion$ = media('(prefers-reduced-motion:reduce)');
  prefersReducedTransparency$ = media('(prefers-reduced-transparency:reduce)');
  prefersReducedData$ = media('(prefers-reduced-data: reduce)');
  prefersContrast$ = media('(prefers-contrast:high)');
  portrait$ = media('(orientation: portrait)');
  landscape$ = media('(orientation: landscape)');
}
<div class="p-4 rounded-xl bg-canvas-shade grid md:grid-cols-2 gap-4">
  <div [ngClass]="{ 'opacity-30': !(prefersDark$ | async) }">
    prefers-color-scheme: dark
  </div>
  <div [ngClass]="{ 'opacity-30': !(prefersLight$ | async) }">
    prefers-color-scheme: light
  </div>
  <div [ngClass]="{ 'opacity-30': !(prefersReducedMotion$ | async) }">
    prefers-reduced-motion: reduce
  </div>
  <div [ngClass]="{ 'opacity-30': !(prefersReducedTransparency$ | async) }">
    prefers-reduced-transparency: reduce
  </div>
  <div [ngClass]="{ 'opacity-30': !(prefersReducedData$ | async) }">
    prefers-reduced-data: reduce
  </div>
  <div [ngClass]="{ 'opacity-30': !(prefersContrast$ | async) }">
    prefers-contract: high
  </div>
  <div [ngClass]="{ 'opacity-30': !(portrait$ | async) }">
    orientation: portrait
  </div>
  <div [ngClass]="{ 'opacity-30': !(landscape$ | async) }">
    orientation: landscape
  </div>
</div>

If you have further questions, feel free to contact me!

Sponsor us

Did you find this post useful? We at notiz.dev write about our experiences developing Apps, Websites and APIs and develop Open Source tools. Your support would mean a lot to us 🙏. Receive a reward by sponsoring us on Patreon or start with a one-time donation on GitHub Sponsors.

Table of Contents

Top of Page Comments Related Articles

Related Posts

Find more posts like this one.

Authors
Marc Stammerjohann
October 17, 2022

Codegen REST API types and requests for Angular

Automatic code generation from OpenAPI 3 for Angular
Angular NestJS Read More
Authors
Marc Stammerjohann
December 15, 2020

Tailwind CSS Purge: Optimize Angular for Production

Remove unused Tailwind CSS utilities from your Angular production build for best performance
Tailwind CSS Angular Scully Read More
Authors
Marc Stammerjohann
November 09, 2020

Firebase Hosting: Preview and Deploy via GitHub Actions

Preview and Deploy your Angular or Scully app on Firebase Hosting automated via GitHub Actions
Firebase Angular GitHub Read More
Authors
Marc Stammerjohann
October 29, 2020

Jamstack: Angular + Scully + Tailwind CSS

Use Angular's static site generator Scully and style it with Tailwind CSS
Scully Angular Tailwind CSS Read More
Authors
Marc Stammerjohann
June 03, 2021

Angular with Tailwind CSS

Learn how to style Angular applications with Tailwind CSS
Angular Tailwind CSS CSS Read More
Authors
Gary Großgarten
June 08, 2020

Create Keyboard Shortcuts with RxJS

The cleanest way to create and orchestrate Keyboard Shortcuts with RxJS.
RxJS Quick Tip Read More
Authors
Gary Großgarten
March 25, 2020

Angular Elements: Create a Component Library for Angular and the Web

Publish Angular components and Custom Elements from a single project! Using the Angular CLI.
Angular Web Components Read More

Sign up for our newsletter

Sign up for our newsletter to stay up to date. Sent every other week.

We care about the protection of your data. Read our Privacy Policy.