@service-work/size-observer

An Angular library for styling DOM elements based on their display size in the browser.

Usage no npm install needed!

<script type="module">
  import serviceWorkSizeObserver from 'https://cdn.skypack.dev/@service-work/size-observer';
</script>

README

Angular Size Observer

NPM version Size when minified & gzipped

The Angular Size Observer package includes a few tools for Angular apps to monitor the display size of elements in the browser. The primary component is the SWObserveSize directive which facilitates styling DOM elements based on their display size. Think CSS media queries, except applying CSS based on DOM element size rather than browser screen size (tangentially related: see the CSS element query spec proposal).

yarn add @service-work/size-observer

# or

npm install --save @service-work/size-observer

Additionally, the Size Observer package makes use of the browser's ResizeObserver API. Depending on what browsers you are targeting (see caniuse.com), you may need to provide a polyfill for ResizeObserver.

Example:

// src/polyfills.ts

import { install as installResizeObserver } from 'resize-observer';

if (!(<any>window).ResizeObserver) {
  installResizeObserver();
}

To install this polyfill: yarn add resize-observer.

There is also resize-observer-polyfill which has more stars on github (there are other polyfills as well). I chose resize-observer for myself solely because it is written in typescript.

Usage

Import the SWSizeObserverModule

Example:

import { SWSizeObserverModule } from '@service-work/size-observer';

@NgModule({
  imports: [SWSizeObserverModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

SWObserveSize Directive

At its most basic, simply apply the SWObserveSize directive to a DOM element and the directive will automatically apply a special CSS class based on the element's display width, and a second CSS class based on the element's display height.

Example:

@Component({
  selector: 'app-root',
  template: `
    <div id="the-div" swObserveSize>
      <!-- div content... -->
    </div>
  `,
})
export class AppComponent {}

Lets assume that the display size of 'the-div' is 1250 px wide and 650 px tall in the above example. SWObserveSize Directive will apply two CSS classes to 'the-div' element:

  • 'sw-container-width-xlarge'.
  • 'sw-container-height-small'.

When the display size of 'the-div' changes in the browser, SWObserveSize will automatically update the CSS classes of 'the-div' as appropriate.

By providing an optional SW_SIZE_OBSERVER_CONFIG config token, you can customize the CSS classes that SWObserveSize applies to elements, along with the width/height breakpoints associated with these classes.

The default config token which is automatically used if you do nothing:

const defaultSizeObserverConfig: ISWSizeObserverConfig = {
  defaultWidthClass: 'sw-container-width-xtiny',
  defaultHeightClass: 'sw-container-height-xtiny',
  widthBreakpoints: new Map([
    [1200, 'sw-container-width-xlarge'],
    [1024, 'sw-container-width-large'],
    [768, 'sw-container-width-medium'],
    [600, 'sw-container-width-small'],
    [500, 'sw-container-width-xsmall'],
    [250, 'sw-container-width-tiny'],
  ]),
  heightBreakpoints: new Map([
    [1200, 'sw-container-height-xlarge'],
    [1024, 'sw-container-height-large'],
    [768, 'sw-container-height-medium'],
    [600, 'sw-container-height-small'],
    [500, 'sw-container-height-xsmall'],
    [250, 'sw-container-height-tiny'],
  ]),
};

This config translates to:

  1. If an element's display width is >= 1200 px, add the 'sw-container-width-xlarge' CSS class to the element.
  2. If an element's display width is >= 1024 px, add the 'sw-container-width-large' CSS class to the element.
  3. If an element's display width is >= 768 px, add the 'sw-container-width-medium' CSS class to the element.
  4. If an element's display width is >= 600 px, add the 'sw-container-width-small' CSS class to the element.
  5. If an element's display width is >= 500 px, add the 'sw-container-width-xsmall' CSS class to the element.
  6. If an element's display width is >= 250 px, add the 'sw-container-width-tiny' CSS class to the element.
  7. Otherwise, add the 'sw-container-width-xtiny' CSS class to the element.

The same logic applies for the display element's height.

SizeObserverService Service

For a bit more flexible usage, you can import the SizeObserverService into a component and manually specify which elements should be observed.

Example:

@Component({
  selector: 'app-root',
  template: `
    <!-- content... -->
  `,
})
export class AppComponent {
  private sizeObserver: SizeObserver;

  constructor(
    private sizeObserverService: SizeObserverService,
    private el: ElementRef<HTMLElement>,
  ) {}

  ngAfterViewInit() {
    this.sizeObserver = this.sizeObserverService.observe(this.el, {
      // specifies that you want the SizeObserverService to automatically
      // apply the appropriate CSS classes to the element
      applyCSS: true,
    });

    this.sizeObserver.widthChanges$.subscribe(size => {
      // do additional stuff on width changes
    });
  }

  ngOnDestroy() {
    // Remember to mark the `SizeObserver` as complete `OnDestroy`
    this.sizeObserver.complete();
  }
}

How to customize the CSS Classes / Breakpoints

This step is optional. If you do nothing, the defaultSizeObserverConfig values will be used (shown above).

If you want to customize the CSS classes / breakpoints which the SWObserveSize directive and/or the SizeObserverService uses, simply re-provide SW_SIZE_OBSERVER_CONFIG.

Example:

import {
  SWSizeObserverModule,
  SW_SIZE_OBSERVER_CONFIG,
  ISWSizeObserverConfig,
} from '@service-work/size-observer';

/**
 * **Important!**
 *
 * Make sure the widthBreakpoint and heightBreakpoint
 * entries are ordered from largest to smallest.
 */

const customSizeObserverConfig: ISWSizeObserverConfig = {
  defaultWidthClass: 'my-smallest-width-container',
  defaultHeightClass: 'my-smallest-height-container',
  widthBreakpoints: new Map([
    [1500, 'my-jumbo-width-container'],
    [1024, 'my-large-width-container'],
    [768, 'my-medium-width-container'],
    [600, 'my-small-width-container'],
  ]),
  heightBreakpoints: new Map([
    [1000, 'my-large-height-container'],
    [800, 'my-medium-height-container'],
    [200, 'my-small-height-container'],
  ]),
};

@NgModule({
  imports: [SWSizeObserverModule],
  declarations: [AppComponent],
  providers: [
    {
      provide: SW_SIZE_OBSERVER_CONFIG,
      useValue: customSizeObserverConfig,
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Interface

SWObserveSize Directive

@Directive({
  selector: '[swObserveSize]',
  exportAs: 'swObserveSize',
})
class SWObserveSize implements AfterViewInit, OnDestroy {
  sizeObserver: SizeObserver;

  /** EventEmitter output of `WidthChange` events  */
  @Output() widthChange = new EventEmitter<WidthChange>();
  /** EventEmitter output of `HeightChange` events  */
  @Output() heightChange = new EventEmitter<HeightChange>();
  /** EventEmitter output of `WidthChange & HeightChange` events  */
  @Output() sizeChange = new EventEmitter<WidthChange & HeightChange>();
}

interface WidthChange {
  width: number;
  widthClass: string;
}

interface HeightChange {
  height: number;
  heightClass: string;
}

SizeObserverService Service

@Injectable()
class SizeObserverService {
  /**
   * Accepts a DOM element and returns a SizeObserver for monitoring
   * the element's display size in the browser.
   *
   * **Options**
   * - `config` - accepts an optional `ISWSizeObserverConfig` object which
   *   will override the default config.
   * - `applyCSS` (default `false`) - when set to true, the appropriate CSS classes will
   *   automatically be applied to the DOM element based on its size.
   *   Otherwise, the DOM element will not have its CSS updated.
   *
   * @param el the element to be observed
   */
  observe(
    el: ElementRef<HTMLElement> | HTMLElement,
    options?: {
      config?: ISWSizeObserverConfig;
      applyCSS?: boolean;
    },
  ): SizeObserver;
}

SizeObserver Class

class SizeObserver {
  /** The px width of this element */
  width: number;
  /** The px height of this element */
  height: number;
  /** The width breakpoint matching this element */
  widthClass: string;
  /** The height breakpoint matching this element */
  heightClass: string;

  /** An observable stream of `WidthChange` events */
  widthChanges$: Observable<WidthChange>;
  /** An observable stream of `HeightChange` events */
  heightChanges$: Observable<HeightChange>;
  /** An observable stream of `WidthChange & HeightChange` events */
  sizeChanges$: Observable<WidthChange & HeightChange>;

  /** A stream of the raw ResizeObserver resize events for this element. */
  rawResizes$: Subject<ResizeObserverEntry>;

  /**
   * You must call this method when you are done observing an
   * element's size (e.g. call this in a component's `ngOnDestroy()`
   * callback).
   *
   * Detaches the underlying `ResizeObserver` from the dom and completes
   * all associated observables.
   */
  complete(): void;
}

About

This library has been made by John Carroll.