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.

The alternative layout library for Flex-box and CSS Grid

The alternative layout library for Flex-box and CSS Grid

If you’ve been looking for an alternative way to write Flexbox or CSS Grid, then Angular’s Flex-Layout might just be the library for you. This post will cover what Angular Flex-Layout is, how to set it up, and a basic overview of the Angular Flex-Layout library.

What is Angular Flex-Layout?

Angular Flex-Layout provides a layout API using Flexbox CSS and mediaQuery. This provides Angular developers with component layout features using a custom Layout API, mediaQuery observables, and injected DOM flexbox CSS Stylings.

The real power of Flex-Layout is the responsive engine. This responsive API enables developers to specify different layouts, sizing, visibilities and viewport sizes, and display devices.

Why choose Angular Flex-Layout?

As the name suggests [Angular Flex-Layout] is a library for laying out your components on your web page. The library does not provide a means for styling, fonts, or colours, as those tasks are delegated to traditional styling in your application. Angular Flex-Layout deals with component positioning and works well with or without Angular Material. It is also built by the Angular team and supported by the community.

Some of the main advantages for using Angular Flex-Layout are:

  • The library is a pure Typescript Layout engine.
  • Uses HTML markup to specify layout configurations.
  • Independent of Angular Material.
  • A responsive API can specify different layouts, sizing, visibilities, viewport sizes, and display devices.
  • Includes CSS Grid.
  • Requires no external stylesheets.

So, let’s have a look at Angular Flex-Layout.

Getting Started.

To install Angular Flex-Layout from the command line type the following into your project directory.

npm install @angular/flex-layout @angular/cdk

Next, we need to import this into app.module.ts. We have a couple of options, we can import both Flexbox and CSS Grid using the FlexLayoutModule or we can specify either FlexModule for Flexbox or GridModule for CSS Grid.

// Flexbox mode (only)
 import {FlexModule} from '@angular/flex-layout/flex';
 // CSS Grid mode (only)
 import {GridModule} from '@angular/flex-layout/grid';
 // Flexbox and CSS Grid (both)
 import {FlexLayoutModule} from '@angular/flex-layout';
 @NgModule({
     …
     imports: [ FlexLayoutModule ],
     …
 });

In this post, we will use the FlexLayoutModule.

Import FlexLayoutModule from the @angular/flex-layout library in your app.module.ts file as shown below.

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 {}

How does Angular Flex-Layout work?

Angular Flex-Layout works by dealing with one row or column at a time. Rows flow across the main axis and columns on the cross axis.

Note: remember that Flexbox is a single-dimensional layout (row or column), where CSS Grid is a two-dimensional layout (row and column).

We start by defining a row or column layout type using the Angular Flex-Layout directive fxLayout= “row” or fxLayout= “column”.

The options are:

API: fxLayout <direction> [wrap]
Allowed values: row | column | row reverse | column reverse
Wrap: is optional and can be applied regardless of the direction.

Content can be positioned on either axis by using another Angular Flex-Layout directive called fxLayoutAlign and this directive requires at least one value (main-axis).

API: fxLayoutAlign <main-axis><cross-axis>
Allowed values:
(main-axis): start* | center | end | space-around | space-between | space-evenly.
(cross-axis): start | center | end | stretch* | space-between | space-around | baseline.

*denotes the default values

The default for fxFlexLayoutAlign is “start stretch” (regardless of whether fxLayout is set to row or column), fxLayoutAlign= “start stretch” can also be shortened to fxLayoutAlign= “start”.

Flexbox — direction reference map

For a default Angular app using Angular Flex-Layout, add the following markup to the app.component.html file.

<div class="outerContainer">
  <div fxLayout="row" fxLayoutGap="10px" class="container">
    <div fxLayoutAlign="center center">1</div>
    <div fxLayoutAlign="center center">2</div>
    <div fxLayoutAlign="center center">3</div>
    <div fxLayoutAlign="center center">4</div>
  </div>
</div>
.outerContainer {
  padding: 5px;
  border: 1px solid #b6b6b6;
  box-sizing: content-box;
}

div.container {
  color: #eeeeee;
  margin-bottom: 10px;
}

div.container > div {
  height: 50px;
  width: 50px;
  background-color: blue;
}

The above markup creates the four numbered boxes shown below in image 1. I’ve added the above CSS to make it easier to see. Let’s walk through the HTML code.

We start with the directive fxLayout= “row” this sets the layout direction to rows. Setting fxLayout to “column” would stack the boxes vertically as shown in image 2.

Next, fxLayoutGap=”10px” sets the margin-right property on the containing DIV elements. The margin-bottom property is set if the layout direction is by columns. This produces a 10pixel gap between each blue box. Each DIV has fxLayoutAlign= “center center” this aligns the content horizontally and vertically, in this instance, the content is the numbers within each DIV.

Image 1: fxLayout=”row”
Image 2: fxLayout= “column”

Flexbox is ideal for moving items or content around the page, but it’s also responsive, so when the browser changes its size the contents on the page change size automatically. Likewise, when the browser is on a smaller (or larger) device the content can be displayed accordingly to the screen size using breakpoints, these breakpoints coincide with CSS mediaQueries. For example:

  • lg = screen and (min-width: 1280px) and (max-width: 1919.99px)
  • lt-xl = screen and (max-width: 1919.99px)
  • gt-md = screen and (min-width: 1280px)
  • gt-sm = screen and (min-width: 960px)
  • gt-xs = screen and (min-width: 600px)
<div class="outerContainer">
  <div fxLayout="row" fxLayout.sm="column" fxLayoutGap="10px" class="container">
    <div fxLayoutAlign="center center">1</div>
    <div fxLayoutAlign="center center">2</div>
    <div fxLayoutAlign="center center">3</div>
    <div fxLayoutAlign="center center">4</div>
  </div>
</div>

In the above code, the fxLayout.sm directive triggers the small breakpoint. When the browser gets to a certain size, for example: screen and (min-width: 600px) and (max-width: 959px) the row of blue boxes now becomes a column of blue boxes. We can also add these breakpoints to other directives like:

  • fxFlexAlign — element-specific overrides on the cross axis.
  • fxLayoutGap — defines padding of child elements in a layout container.
  • fxLayoutAlign — defines the positioning of child elements along the main and cross axis in a layout container.
  • fxFlexOrder — configures the positional ordering of the element in a sorted layout container.
  • fxFlexOffset — configures the “margin-left” of the element in a layout container.

Each of the above directives can include one or more of the following breakpoints.

.xs, .sm, .md, .lg, .xl, .lt-sm, .lt-md, .lt-lg, .lt-xl, .gt-xs, .gt-sm, .gt-md, .gt-lg

For example fxLayout.xs fxLayout.sm fxLayout.lt-md. There is a breakpoint to cover almost every possible display scenario.

The fxFlex directive resizes elements horizontally or vertically. We can specify this directive in one of two ways:

fxFlex= “<grow> <shrink> <basis>”

  • grow: defines how much an item should grow, if space is available.
  • shrink: defines how much an item should shrink if there is not enough space available.
  • basis: controls the default size of an element, before it is manipulated by other properties.

or using the shorthand method:

  • fxFlex= “” (or just fxFlex)
  • fxFlex= “1 1 5em”
  • fxFlex= “1 1 calc(5em + 5px)”
  • fxFlex= “1 1 auto”

Think of this shorthand version as MaxMin and Ideal.

As an example, we set the second DIV (line 4, in code below) to fxFlex= “2 1 auto”. This means that this DIV will take up twice the space as the other DIVs.

<div class="outerContainer">
  <div fxLayout="row" fxLayoutGap="1px" fxLayout.sm="column" class="container">
    <div fxLayoutAlign="center center">1</div>
    <div fxFlex="2 1 auto" fxLayoutAlign="center center">2</div>
    <div fxLayoutAlign="center center">3</div>
    <div fxLayoutAlign="center center">4</div>
  </div>
</div>

Image 3 using fxFlex

For the shrink, we have set this to one this means use the same space at all times, if we had set this to zero it wouldn’t shrink at all.

fxFlex=”2 0 auto”

The third value is set to ‘auto’ in the above example. The width or height of the content is used as the ideal size. This setting is shown in the following markup (line 4):

<div class="outerContainer">
  <div fxLayout="row" fxLayoutGap="1px" fxLayout.sm="column" class="container">
    <div fxLayoutAlign="center center">1</div>
    <div fxFlex="0 0 auto" fxLayoutAlign="center center">2</div>
    <div fxLayoutAlign="center center">3</div>
    <div fxLayoutAlign="center center">4</div>
  </div>
</div>

The default output appears below:

fxFlex=”0 0 auto”

We have covered most of the main directives in the Angular Flex-Layout library, there are a few others such as fxFlexOrderfxFlexFill, fxFlexOffset and fxFlexAlign which I may cover in a later post.

Summary

There is a lot more to the Angular Flex-Layout library than what I have covered here. The library also includes full CSS Grid support (though this is somewhat currently lacking in documentation, it is something I’m working to improve on). I will be doing a post on Angular Flex-Layout CSS Grid at a later date.