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...)
npm i @angular/cdk // install the angular cdk
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
}
}
}
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)`
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>
Here you can see a working example
Top comments (0)