Skip to content

[Tailwind | Angular] Responsive breakpoints in typescript

Tailwind breakpoints in typescript with an easy angular component

Georg Höller
Georg Höller
3 min read
[Tailwind | Angular] Responsive breakpoints in typescript
Photo by Blake Connally / Unsplash

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 :)
AngularTailwind

Comments


Related Posts

Members Public

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n

Install packages npm i -D @nx/storybook npm i -D @storybook/angular Create config nx g @nx/storybook:configuration <project-name> I created the config inside the main app to share some configs/modules. My stories are located at libs/shared/ui. That's why I had to

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n
Members Public

[Angular | RxJS] BehaviorSubject with custom states

Whenever I call an api, often my frontend has to go through some different visualization steps. Imagine you have some kind of search input and a list which displays the results but you want to show an loading indicator, an error hint and something different when the result is empty.

[Angular | RxJS] BehaviorSubject with custom states
Members Public

[Angular] Dynamic App Config and Translations in a Submodule

When I'm setting up a new angular application, I always start with translation support and an external app config json loader. You should never start a new app without an internationalization (i18n) system - even if you only support one language! We are going to use the package

[Angular] Dynamic App Config and Translations in a Submodule