Angular Flex-Layout using MediaObserver

In the last post, we covered setting up and using the Angular Flex-Layout library. In that post, we looked at the responsive aspect of the library using directives and suffixing them with breakpoints where required.

To recap, add the fxLayout directive to a DOM element in the HTML. Then, add the abbreviated breakpoint depending on where the view size should change. For example, fxLayout.md or fxFlexAlign.lt-lg etc…

This post covers how to use the MediaObserver service.

The mediaObserver is installed from the Angular Flex-Layout library as shown below. In the projects terminal window type:

npm install @angular/flexlayout
view raw addFlex-Layout.ts hosted with ❤ by GitHub

And then import it to our app.module.ts file.

import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
import { FlexLayoutModule } from "@angular/flex-layout";
@NgModule({
imports: [BrowserModule, FormsModule, FlexLayoutModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
view raw app.module.ts hosted with ❤ by GitHub

The MediaObserver service is an Observable that exposes features to subscribe to mediaQuery changes. It also serves as an isActive() validator method to check if a mediaQuery is currently active.

The MediaObserver service has two API’s.
asObservable(): Observable<MediaChange>
isActive(query: string): boolean

We use Angular Dependency Injection to inject a reference to the MediaObserver as a constructor parameter to use this service.

import { Component, OnDestroy, OnInit } from '@angular/core';
import { MediaChange, MediaObserver } from '@angular/flex-layout';
import { Subscription } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, OnDestroy {
title = 'Angular Flex-Layout';
/**
*
* @param mediaObserver
*/
constructor(public mediaObserver: MediaObserver) {}
private mediaSubscription!: Subscription;
private activeMediaQuery: string = '';
ngOnInit(): void {
const getAlias = (MediaChange: MediaChange[]) => {
return MediaChange[0].mqAlias;
};
this.mediaSubscription = this.mediaObserver
.asObservable()
.pipe(
distinctUntilChanged(
(x: MediaChange[], y: MediaChange[]) => getAlias(x) === getAlias(y)
)
)
.subscribe((change) => {
change.forEach((item) => {
this.activeMediaQuery = item
? `'${item.mqAlias}' = (${item.mediaQuery})`
: '';
if (item.mqAlias === 'md') {
this.loadMobileContent();
}
console.log('activeMediaQuery', this.activeMediaQuery);
});
});
}
ngOnDestroy(): void {
this.mediaSubscription.unsubscribe();
}
loadMobileContent() {
console.log('load mobile content');
// Do something special since the viewport is currently
// using mobile display sizes.
}
}
view raw mediaobservable.ts hosted with ❤ by GitHub

The current example on the Angular Flex-Layout’s wiki page is a little out of date. The media$ observable is now deprecated and replaced with mediaObserver. There appears to be a small bug when reporting the change detection as it duplicates the results, as shown below.

mediaObserver — showing duplicates

To get around this I’ve added a call to the distinctUntilChanged() RXJS operator (lines 29–31). The operator now only emits a single change.

Resize the browser to log the current breakpoint.

console.log of active breakpoint

Here the screen size is small (sm), but also shows us that the screen is less than medium (lt-md), less than large (lt-lg), less than extra-large (lt-xl), and greater than extra-small (gt-xs). In most cases, we really only need to know the actual size, but it’s good to know the others just in case.

So how do we use the MediaObserver?

When designing our applications, we need to know when the screen view size changes so that we can change our UI to make optimal use of the space. This might mean hiding an element or switching from rows to columns, for example:

<div fxLayout="row" fxLayoutGap="10px">
<div fxFlex="1 0 10" *ngIf="mediaObserver.isActive('md')"
id="boxone" style="background: red;">Box One</div>
<div fxFlex="1 0 10" id="boxtwo" style="background: blue;">Box Two</div>
</div>
view raw mediaobservable.html hosted with ❤ by GitHub

When the browser hits the ‘md’ breakpoint, the DIV (starting on line 2) is removed from the DOM.

To use the mediaObserver in your HTML (as shown above) it needs to be accessed by a get() for it to be visible.

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, OnDestroy {
title = 'Angular Flex-Layout';
get media() {
return this.mediaObserver;
}
}
view raw mediaGetter.ts hosted with ❤ by GitHub
Getter for accessing the mediaObserver

Let’s try out another example, you will need to install Angular Material for this to work:

import { Component, OnInit } from '@angular/core';
import { MediaChange, MediaObserver } from '@angular/flex-layout';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
constructor(private mediaObserver: MediaObserver) {}
cols: Observable<any> | undefined;
ngOnInit(): void {
const grid = new Map([
['xs', 1],
['sm', 2],
['md', 3],
['lg', 4],
['xl', 5],
]);
this.cols = this.mediaObserver.asObservable().pipe(
map((change: MediaChange[]) => {
console.log(change[0]);
console.log(grid.get(change[0].mqAlias));
return grid.get(change[0].mqAlias);
})
);
}
}
view raw mediaObserver.ts hosted with ❤ by GitHub

Let’s walk through the above code:

We inject the mediObserver into the constructor as before, then create a new Map of the breakpoints. We set up an observable to the mediaObserver using the asObservable(). When the browser is resized, we get the currently set breakpoint from mqAlias, get the matching value from the grid array, and assign all of this to the cols variable.

<mat-grid-list rowHeight="1:1" [cols]="cols | async" gutterSize="6px">
<mat-grid-tile> {{1}} </mat-grid-tile>
<mat-grid-tile> {{2}} </mat-grid-tile>
<mat-grid-tile> {{3}} </mat-grid-tile>
<mat-grid-tile> {{4}} </mat-grid-tile>
<mat-grid-tile> {{5}} </mat-grid-tile>
</mat-grid-list>
view raw mediaObserver.html hosted with ❤ by GitHub

In the HTML we set up an Angular Material grid list component and use the async pipe operator to subscribe to the cols observable. The grid shrinks as the browser width decreases. Tile rows wrap to the row below until only one tile can fit on a page. When the browser width increases, the tiles move up to form one long row.

Output when the browser is resized.

The number we get back from the grid Map denotes the number of columns shown in our grid list, here the browser size is ‘md’ or ‘3’ columns.

Using the mediaObserver we now have an observable that we can subscribe to in our application and track when our viewport changes. The mediaObserver can be used in both the HTML or the Typescript code behind and we walked through a couple of examples in this post.

Thanks for reading.