Jecelyn Yeen
Most of the time, we use CSS media queries to handle responsive, screen size changes to layout our content differently. However, there are times where CSS media queries alone isn’t sufficient for that. We need to handle the responsiveness in our code.
In this article, I would like to share about how to detect responsive breakpoints in Angular, with a twist - we don’t maintaining responsive breakpoint sizes in your Typescript code (because responsive breakpoints are already defined in CSS).
We will use Angular with Bootstrap in this example, but it works for any CSS frameworks and classes. Let’s start.
We will be using CSS Classes to determine the current responsive breakpoints. There are 5 breakpoints in Bootstrap CSS. The CSS classes to determine the visibility of each breakpoints is:
.d-block .d-sm-none
.d-none .d-sm-block .d-md-none
.d-none .d-md-block .d-lg-none
.d-none .d-lg-block .d-xl-none
.d-none .d-xl-block
The CSS display
property will be toggled between none
or block
. We will apply these classes to HTML elements.
Everytime when screen size changes, we will loop and find the HTML element with style display: block
, this is how we will detect the current breakpoint.
Here is the code if you are too excited to see the solution: https://stackblitz.com/edit/angular-size.
Let’s create an Angular component size-detector
.
The component HTML template:
<!-- size-detector.component.html -->
<div *ngFor="let s of sizes" class="{{s.css + ' ' + (prefix + s.id) }}">{{s.name}}</div>
The component Typescript code:
// size-detector.component.ts
...
export class SizeDetectorComponent implements AfterViewInit {
prefix = 'is-';
sizes = [
{
id: SCREEN_SIZE.XS, name: 'xs', css: `d-block d-sm-none`
},
{
id: SCREEN_SIZE.SM, name: 'sm', css: `d-none d-sm-block d-md-none`
},
{
id: SCREEN_SIZE.MD, name: 'md', css: `d-none d-md-block d-lg-none`
},
{
id: SCREEN_SIZE.LG, name: 'lg', css: `d-none d-lg-block d-xl-none`
},
{
id: SCREEN_SIZE.XL, name: 'xl', css: `d-none d-xl-block`
},
];
@HostListener("window:resize", [])
private onResize() {
this.detectScreenSize();
}
ngAfterViewInit() {
this.detectScreenSize();
}
private detectScreenSize() {
// we will write this logic later
}
}
After looking at the component code, you might be wondering where is those SCREEN_SIZE.*
value come from. It is an enum. Let’s create the screen size enum
(You may create a new file or just place the enum in same component file)
// screen-size.enum.ts
/_ An enum that define all screen sizes the application support _/
export enum SCREEN_SIZE {
XS,
SM,
MD,
LG,
XL
}
Also, remember to add Bootstrap to your project! You may add it via npm or yarn, but in this example, we will use the easier way. Add the cdn link in index.html
.
<!-- index.html -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
The code is pretty expressive itself.
sizes
that we support and the CSS classes that used to determine each breakpoints.div
element, assign css and display it. Also note that we give each div
an additional unique css class is-<SIZE_ENUM>
.detectScreenSize
. This is where we will write our logic to detect the screen size changes. We will complete that later.HostListener
decorator to listen to the window resize
event.AfterViewInit
component lifecycle hook.Now we have the component code “almost” ready, let’s start implementing our resize service
.
// resize.service.ts
@Injectable()
export class ResizeService {
get onResize$(): Observable<SCREEN_SIZE> {
return this.resizeSubject.asObservable().pipe(distinctUntilChanged());
}
private resizeSubject: Subject<SCREEN_SIZE>;
constructor() {
this.resizeSubject = new Subject();
}
onResize(size: SCREEN_SIZE) {
this.resizeSubject.next(size);
}
}
The resize service code is simple:
resizeSubject
.onResize
that receive size
as the parameter. It will then push the value to the resize stream. (We will call this method later in our size-detector
component)distinctUntilChanged
operator in the resize observable. We use that to reduce unnecessary notification. For example, when your screen size change from 200px to 300px, it is still consider as xs
size in bootstrap. We don’t need to notify in that case. (You can remove the operator if you need)onResize$
. Any components, services, directives, etc can then subscribe to this stream to get notify whenever size is changed.Next, let’s go back to our size-detector
component and update the detectScreenSize
logic.
// size-detector.component.ts
...
private detectScreenSize() {
constructor(private elementRef: ElementRef, private resizeSvc: ResizeService) { }
const currentSize = this.sizes.find(x => {
// get the HTML element
const el = this.elementRef.nativeElement.querySelector(`.${this.prefix}${x.id}`);
// check its display property value
const isVisible = window.getComputedStyle(el).display != 'none';
return isVisible;
});
this.resizeSvc.onResize(currentSize.id);
}
...
Let’s breakdown and go through the logic together:
ElementRef
and our newly created ResizeService
to our component.sizes
array and find it.sizes
array, we will use HTML5 element’s query selector to find the element by the unique css class we defined earlier on is-<SIZE_ENUM>
.onResize
method.You may place the size-detector
component under our root component app-component
. For example:
<!-- app.component.html -->
<hello name="{{ name }}"></hello>
<!-- Your size-detector component place here -->
<app-size-detector></app-size-detector>
In this example, I have another hello-component
in the app-component
, but that doesn’t matter.
Since I place the component in app-component
, means I can use the ResizeService
everywhere (directives, components, services, etc).
For instance, let’s say I want to detect the screen size changes in hello-component
, I can do so by inject the ResizeService
in constructor, then subscribe to the onSizeChange$
observable and do what I need.
// hello.component.ts
@Component({
selector: 'hello',
template: `<h1>Hello {{size}}!</h1>`,
})
export class HelloComponent {
size: SCREEN_SIZE;
constructor(private resizeSvc: ResizeService) {
// subscribe to the size change stream
this.resizeSvc.onResize$.subscribe(x => {
this.size = x;
});
}
}
In the above code, we detect the screen size changes and simply display the current screen size value.
See it in action!
One of the real life use case scenario might be you have accordion on screen. In mobile, you would like to collapse all accordion panels, show only the active one at a time. However, in desktop, you might want to expand all panel.
This is how we can detect the screen size changes without maintaining the actual breakpoint sizes in our JavaScript code. Here is the code: https://stackblitz.com/edit/angular-size.
If you think of it, it is not very often that the user changes the screen size when browsing the app. You may handle the screen sizes changes application wide (like our example above) or just handle it everytime you need it (per use case / component basis).
Besides that, if you don’t mind to duplicate and maintain the breakpoint sizes in JavaScript code, you may remove the component, move the detectScreenSize
into your service and change a bit on the logic. It is not difficult to implement that. (Try it probably?)
That’s all. Happy coding!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
just fyi, you added the constructor
constructor(private elementRef: ElementRef, private resizeSvc: ResizeService) { }
to the body of thedetectSizeChanges
function