Fri. Mar 5th, 2021

Recently I needed to be able to switch between parts of my HTML based on the screen resolution. This is something most web developers will run into needing to support Mobile and Desktop systems. I got to thinking about it, and was thinking how nice it would be if *ngIf=”screen:sm” or something like that. I mean wouldn’t it be great to have a nice directive to do this for you?

*onSize was born

Well wouldn’t it be nice if we had a *onSize directive that we could use everywhere to enable different elements as necessary?

Well I got to thinking about it, and sat down one night and came up with this:

import {Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef} from '@angular/core';
import {BreakpointObserver, Breakpoints, BreakpointState} from "@angular/cdk/layout";
import {Subscription} from "rxjs";

@Directive({
  selector: '[onSize]'
})
export class OnSizeDirective implements OnDestroy{
  private hasView = false;
  private subscription: Subscription;
  private _sizes:string[] = [];
  private result: BreakpointState | undefined;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private breakpointObserver: BreakpointObserver) {

    this.subscription = this.breakpointObserver.observe([
      Breakpoints.XSmall,
      Breakpoints.Small,
      Breakpoints.Medium,
      Breakpoints.Large,
      Breakpoints.XLarge
    ]).subscribe(result => {
      this.result = result;
      this.updateView();
    });
  }


  @Input() set onSize(sizes:string[]) {
    this._sizes = sizes;
    this.updateView();
  }

  private updateView() {
    let viewChange = false;

    if(this.result != undefined) {
      if ( (this.result.breakpoints[Breakpoints.XSmall] && this.containsSize("xs"))
            || (this.result.breakpoints[Breakpoints.Small] && this.containsSize("sm"))
            || (this.result.breakpoints[Breakpoints.Medium] && this.containsSize("md"))
            || (this.result.breakpoints[Breakpoints.Large] && this.containsSize("lg"))
            || (this.result.breakpoints[Breakpoints.XLarge] && this.containsSize("xl"))) {
        viewChange = true;
      }
    }

    this.setView(viewChange);
  }

  setView(condition:boolean) {
    if (condition && !this.hasView) {
      this.viewContainer.clear();
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (!condition && this.hasView) {
      this.viewContainer.clear();
      this.hasView = false;
    }
  }

  containsSize(size:string): boolean {
    return this._sizes.filter(item => item === size).length > 0;
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

So let’s start breaking this down. First we need to create a directive:

ng g d directives/on-size

Now we need to make this a structural directive and we do that be adding TemplateRef<any> and ViewContainerRef to the constructor. If you need more information on this, check here.

Next, we need to know what sizes you want to display under. For this implementation we choose Extra Small(xs), Small(sm), Medium(md), Large(lg), and Extra Large(xl) as my sizes. These come directly from the BreakpointObserver API.

Now we add the BreakpointObserver to the constructor. Then we observe (), for all the types we are interested in. Make sure you save this subscription off so that you unsubscribe when the directive is destroyed.

Now to make things simple we set an @Input set onSize() to take a string array. Whenever the BreakpointObserver or onSize() changes we call updateView() to check if we match on the screen size, if we do then call setView().

setView() will check if we have the view and if the condition was met.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.