Angular 2: Structural directive for responsive conditional output using a CSS media query

Media queries are useful to adapt the way elements on a page are displayed, or even show/hide elements based on the viewport size.

When working with Angular 2, there might be times where you not only want to conditionally show or hide an element depending on the viewport size, but you also need to make sure that your component does not even get instantiated when the app is run on a mobile or desktop. Not creating components when they are not needed has obvious performance benefits!

Enter the ngIfMediaQuery structural directive

At the end of this article is the code for a simple structural directive I wrote that will conditionally create a component based on the result of a CSS media query.

The directive can be used as such:

<!-- div will only exist when the media query matches -->
<div *ngIfMediaQuery="'(min-width: 500px)'">
    ...
</div>

This is conditionally creating a simple div element, but as with any other structural directive, it could be used on any component really.

TIP

Be sure to add the directive to the declarations section of your NgModule before using it in your application.

Here’s the code:

import { Directive, TemplateRef, ViewContainerRef } from "@angular/core";
import { isBlank } from "@angular/core/src/facade/lang";
/**
* How to use this directive?
*
* ```
* <div *ngIfMediaQuery="'(min-width: 500px)'">
* Div element will exist only when media query matches, and created/destroyed when the viewport size changes.
* </div>
* ```
*/
@Directive({
selector: '[ngIfMediaQuery]',
inputs: ['ngIfMediaQuery']
})
export class NgIfMediaQuery {
private prevCondition: boolean = null;
private mql: MediaQueryList;
private mqlListener: (mql: MediaQueryList) => void; // reference kept for cleaning up in ngOnDestroy()
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<Object>) {}
/**
* Called whenever the media query input value changes.
*/
set ngIfMediaQuery(newMediaQuery: string) {
if (!this.mql) {
this.mql = window.matchMedia(newMediaQuery);
/* Register for future events */
this.mqlListener = (mq) => {
this.onMediaMatchChange(mq.matches);
};
this.mql.addListener(this.mqlListener);
}
this.onMediaMatchChange(this.mql.matches);
}
ngOnDestroy() {
this.mql.removeListener(this.mqlListener);
this.mql = this.mqlListener = null;
}
private onMediaMatchChange(matches: boolean) {
// this has been taken verbatim from NgIf implementation
if (matches && (isBlank(this.prevCondition) || !this.prevCondition)) {
this.prevCondition = true;
this.viewContainer.createEmbeddedView(this.templateRef);
} else if (!matches && (isBlank(this.prevCondition) || this.prevCondition)) {
this.prevCondition = false;
this.viewContainer.clear();
}
}
}