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 isapp
.--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.