Skip to content

How to Manage Breakpoints using BreakpointObserver in Angular

Defining Breakpoints is important when you start working with Responsive Design and most of the time they're created using CSS code. For example:

.title {
  font-size: 12px;
}

@media (max-width: 600px) {
  .title {
    font-size: 14px;
  }
}

By default, the text size value will be 12px, and this value will be changed to 14px when the viewport gets changed to a smaller screen (a maximum width of 600px).

That solution works. However, what about if you need to listen for certain breakpoints to perform changes in your application? This may be needed to configure third-party components, processing events, or any other.

Luckily, Angular comes with a handy solution for these scenarios: the BreakpointObserver. Which is a utility for checking the matching state of @media queries.

In this post, we will build a sample application to add the ability to configure certain breakpoints, and being able to listen to them.

Project Setup

Prerequisites

You'll need to have installed the following tools in your local environment:

  • Node.js. Preferably the latest LTS version.
  • A package manager. You can use either NPM or Yarn. This tutorial will use NPM.

Creating the Angular Project

Let's start creating a project from scratch using the Angular CLI tool.

ng new breakpointobserver-example-angular --routing --prefix corp --style css --skip-tests

This command will initialize a base project using some configuration options:

  • --routing. It will create a routing module.
  • --prefix corp. It defines a prefix to be applied to the selectors for created components(corp in this case). The default value is app.
  • --style scss. The file extension for the styling files.
  • --skip-tests. it avoids the generations of the .spec.ts files, which are used for testing

Adding Angular Material and Angular CDK

Before creating the breakpoints, let's add the Angular Material components, which will install the Angular CDK library under the hood.

ng add @angular/material

Creating the Home Component

We can create a brand new component to handle a couple of views to be updated while the breakpoints are changing. We can do that using the ng generate command.

ng generate component home

Pay attention to the output of the previous command since it will show you the auto-generated files.

Update the Routing Configuration

Remember we used the flag --routing while creating the project? That parameter has created the main routing configuration file for the application: app-routing.module.ts. Let's update it to be able to render the home component by default.

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  {
    path: '',
    component: HomeComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Update the App Component template

Remove all code except the router-outlet placeholder:

<!-- app.component.html -->
<router-outlet></router-outlet>

This will allow rendering the home component by default once the routing configuration is running.

Using the BreakpointObserver

The application has the Angular CDK installed already, which has a layout package with some utilities to build responsive UIs that react to screen-size changes.

Let's update the HomeComponent, and inject the BreakpointObserver as follows.

//home.component.ts
import { Component, OnInit } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';

@Component({
  selector: 'corp-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  readonly breakpoint$ = this.breakpointObserver
    .observe([Breakpoints.Large, Breakpoints.Medium, Breakpoints.Small, '(min-width: 500px)'])
    .pipe(
      tap(value => console.log(value)),
      distinctUntilChanged()
    );

  constructor(private breakpointObserver: BreakpointObserver) { }

  ngOnInit(): void {
  }
}

Once the BreakpointObserver is injected, we'll be able to evaluate media queries to determine the current screen size, and perform changes accordingly.

Then, a breakpoint$ variable references an observable object after a call to the observe method.

The observe method gets an observable of results for the given queries, and can be used along with predetermined values defined on Breakpoints as a constant.

Also, it's possible to use custom breakpoints such as (min-width: 500px). Please refer to the documentation to find more details about this.

Next, you may need to subscribe to the breakpoint$ observable to see the emitted values after matching the given queries.

Again, let's update the home.component.ts file to do that.

// home.component.ts

// .... other imports
import { distinctUntilChanged, tap } from 'rxjs/operators';

@Component({
  //....
})
export class HomeComponent implements OnInit {

  Breakpoints = Breakpoints;
  currentBreakpoint:string = '';

  // ... readonly breakpoint$ = this.breakpointObserver

  constructor(private breakpointObserver: BreakpointObserver) { }

  ngOnInit(): void {
    this.breakpoint$.subscribe(() =>
      this.breakpointChanged()
    );
  }
  private breakpointChanged() {
    if(this.breakpointObserver.isMatched(Breakpoints.Large)) {
      this.currentBreakpoint = Breakpoints.Large;
    } else if(this.breakpointObserver.isMatched(Breakpoints.Medium)) {
      this.currentBreakpoint = Breakpoints.Medium;
    } else if(this.breakpointObserver.isMatched(Breakpoints.Small)) {
      this.currentBreakpoint = Breakpoints.Small;
    } else if(this.breakpointObserver.isMatched('(min-width: 500px)')) {
      this.currentBreakpoint = '(min-width: 500px)';
    }
  }
}

In the above code, the ngOnInit method is used to perform a subscription to the breakpoint$ observable and the method breakpointChanged will be invoked every time a breakpoint match occurs.

As you may note, the breakpointChanged method verifies what Breakpoint value has a match through isMatched method. In that way, the current component can perform changes after a match happened (in this case, it just updates the value for the currentBreakpoint attribute).

Using Breakpoint values on the Template

Now, we can set a custom template in the home.component.html file and be able to render a square according to the currentBreakpoint value.

<!-- home.component.html -->

<mat-card style="text-align: center; margin: 20px">
  {{ currentBreakpoint }}
</mat-card>

<div class="container" [ngSwitch]="currentBreakpoint">
  <span class="large" *ngSwitchCase="Breakpoints.Large"> Large </span>
  <span class="medium" *ngSwitchCase="Breakpoints.Medium"> Medium </span>
  <span class="small" *ngSwitchCase="Breakpoints.Small"> Small </span>
  <span class="custom" *ngSwitchCase="'(min-width: 500px)'"> Custom </span>
</div>

The previous template will render the current media query value at the top along with a rectangle according to the size: Large, Medium, Small or Custom.

Live Demo and Source Code

Want to play around with this code? Just open the Stackblitz editor or the preview mode in fullscreen.

Find the complete angular project in this GitHub repository: breakpointobserver-example-angular. Do not forget to give it a star ā­ļø and play around with the code.

Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.