[Tailwind | Angular] Responsive breakpoints in typescript

Recently I stumbled over a problem with my tailwind breakpoints. For my sidebar component, I need to know in typescript which tailwind breakpoint is active.
I did what I do best - searched for a solution in stackoverflow. I found the following answer - which is the source for my tw-breakpoints angular component.

How do I get tailwinds active breakpoint in JavaScript?
I am building tailwind with config file and including it in a react project. I would like to get the active breakpoint value in javascript/React. How can I achieve the same.? <div class=”...

Solution

If you just want to see the final code to get tailwind breakpoints in typescript, check out the repo:

gh-utils/src/lib/tailwind-helpers/tw-breakpoints at main · Georg632/gh-utils
Contribute to Georg632/gh-utils development by creating an account on GitHub.

I created a new standalone component with the name tw-breakpoints:

ng g c tw-breakpoints --skipTests --skipImport --standalone

The template is quite simple:

<div
  #twBreakpointContainer
  class="absolute w-full left-0 right-0 h-0 opacity-0"
>
  <div #sm class="absolute block md:hidden"></div>
  <div #md class="absolute hidden md:block lg:hidden"></div>
  <div #lg class="absolute hidden lg:block xl:hidden"></div>
  <div #xl class="absolute hidden xl:block 2xl:hidden"></div>
  <div #xxl class="absolute hidden 2xl:block"></div>
</div>

I'm using the container div element for the ResizeObserver and it is responsible for making the component invisible.
Add a child for each breakpoint which should be available in typescript. Only one child should be visible at the same time.

Let's have a look at the typescript code:

import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  EventEmitter,
  Output,
  ViewChild,
} from '@angular/core';
import { CommonModule } from '@angular/common';

export enum TwBreakpoints {
  sm,
  md,
  lg,
  xl,
  xxl,
}

@Component({
  selector: 'tw-breakpoints',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [CommonModule],
  templateUrl: './tw-breakpoints.component.html',
})
export class TwBreakpointsComponent {
  @ViewChild('twBreakpointContainer', { static: true }) container!: ElementRef;
  @ViewChild('sm', { static: true }) sm!: ElementRef;
  @ViewChild('md', { static: true }) md!: ElementRef;
  @ViewChild('lg', { static: true }) lg!: ElementRef;
  @ViewChild('xl', { static: true }) xl!: ElementRef;
  @ViewChild('xxl', { static: true }) xxl!: ElementRef;

  @Output('breakpointChange') breakpointChange: EventEmitter<TwBreakpoints> =
    new EventEmitter<TwBreakpoints>();

  resizeObserver!: ResizeObserver;

  constructor() {}

  ngOnInit(): void {
    this.onResize();

    this.resizeObserver = new ResizeObserver((entries) => {
      this.debounce(() => {
        if (entries?.length > 0) this.onResize();
      });
    });
    this.resizeObserver.observe(this.container.nativeElement);
  }

  onResize() {
    if (this.offsetParentSet(this.sm)) {
      console.log('sm');
      this.breakpointChange.emit(TwBreakpoints.sm);
    }
    if (this.offsetParentSet(this.md)) {
      console.log('md');
      this.breakpointChange.emit(TwBreakpoints.md);
    }
    if (this.offsetParentSet(this.lg)) {
      console.log('lg');
      this.breakpointChange.emit(TwBreakpoints.lg);
    }
    if (this.offsetParentSet(this.xl)) {
      console.log('xl');
      this.breakpointChange.emit(TwBreakpoints.xl);
    }
    if (this.offsetParentSet(this.xxl)) {
      console.log('2xl');
      this.breakpointChange.emit(TwBreakpoints.xxl);
    }
  }

  offsetParentSet(el: ElementRef): boolean {
    return el.nativeElement.offsetParent != null;
  }

  ngOnDestroy(): void {
    this.resizeObserver.disconnect();
  }

  debounceResizeTimeout: any = null;
  private debounce(fn: Function) {
    if (this.debounceResizeTimeout) {
      clearTimeout(this.debounceResizeTimeout);
    }
    this.debounceResizeTimeout = setTimeout(function () {
      fn();
    }, 150);
  }
}

The ResizeObserver observes the container element - when the size changes it triggers the onResize function.
Here starts the interessting part. If the offsetParent property is null, the tailwind breakpoint from this element is not met.
Just emit the output eventemitter with the correct breakpoint and your parent component can handle the windows size change.

If you find a better solution for this problem - give me a hint in the comments!

If you have further questions about this topic or need help with a problem in general, please write a comment or simply contact me at yesreply@georghoeller.dev :)