DEV Community

Junaid Ramzan
Junaid Ramzan

Posted on • Edited on

Render content based on viewport size with a directive

In this post I am going to describe how to use angular https://material.angular.io/cdk/layout/overview to build a structural directive that controls the rendering of components.

Structural directives are directives which change the DOM layout by adding and removing DOM elements. They are prefixed with the asterisk symbol(*). You may have used (*ngIf, *ngSwitch...)

Detailed explanation here

npm i @angular/cdk // install the angular cdk
Enter fullscreen mode Exit fullscreen mode
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'
import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core'
import { Subscription } from 'rxjs'

type BreakpointSizes = 'XSmall' | 'Small' | 'Medium' | 'Large' | 'XLarge' | 'Desktop' | `(${'max-width'|'min-width'}: ${number}px)`

const sizes = new Map([
  ['XSmall', Breakpoints.XSmall],
  ['Small', Breakpoints.Small],
  ['Medium', Breakpoints.Medium],
  ['Large', Breakpoints.Large],
  ['XLarge', Breakpoints.XLarge]
])

@Directive({
  standalone: true,
  selector: '[appIfViewportMatch]'
})
export class IfViewportMatchDirective implements OnDestroy {
  private subscription!: Subscription
  private hasView = false

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

  @Input() set appIfViewportMatch(mq: BreakpointSizes) {
    if (this.subscription) return
    const size = sizes.get(mq)

    this.subscription = this.bpObserver.observe(size || mq).subscribe(({ matches }) => {
      this.render(matches)
    })
  }

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

  private render(matches: boolean) {
    if (!this.hasView && matches) {
      this.viewContainer.createEmbeddedView(this.templateRef)
      this.hasView = true
    } else  {
      this.viewContainer.clear()
      this.hasView = false
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The directive listens for viewport sizes and when a media query is matched the content is rendered into the DOM.

It only subscribes once on the first set to avoid multiple subscriptions.

This type provides some intellisense for the accepted values. It also provides the option to provide a custom media query.

type BreakpointSizes = 'XSmall' | 'Small' | 'Medium' | 'Large' | 'XLarge' | 'Desktop' | `(${'max-width'|'min-width'}: ${number}px)`
Enter fullscreen mode Exit fullscreen mode

Examples:

<!-- Only renders when the viewport is more than 600px -->
<hello name="{{ name }}" *appIfViewportMatch="'(min-width: 600px)'"></hello>

<!-- Mobile view -->
<h1 *appIfViewportMatch="'XSmall'">On mobile</h1>
Enter fullscreen mode Exit fullscreen mode

Here you can see a working example

Top comments (0)