Detecting real time window size changes in Angular 4
HtmlAngularDom EventsHtml Problem Overview
I have been trying to build a responsive nav-bar and do not wish to use a media query, so I intend to use *ngIf
with the window size as a criterion.
But I have been facing a problem as I am unable to find any method or documentation on Angular 4 window size detection. I have also tried the JavaScript method, but it is not supported.
I have also tried the following:
constructor(platform: Platform) {
platform.ready().then((readySource) => {
console.log('Width: ' + platform.width());
console.log('Height: ' + platform.height());
});
}
...which was used in ionic.
And screen.availHeight
, but still no success.
Html Solutions
Solution 1 - Html
To get it on init
public innerWidth: any;
ngOnInit() {
this.innerWidth = window.innerWidth;
}
If you wanna keep it updated on resize:
@HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
}
Solution 2 - Html
If you want to react on certain breakpoints (e.g. do something if width is 768px or less), you can use BreakpointObserver
:
import { Component } from '@angular/core';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private breakpointObserver: BreakpointObserver,
) {
// detect screen size changes
this.breakpointObserver.observe([
"(max-width: 768px)"
]).subscribe((result: BreakpointState) => {
if (result.matches) {
// hide stuff
} else {
// show stuff
}
});
}
}
Solution 3 - Html
This is an example of service which I use.
You can get the screen width by subscribing to screenWidth$
, or via screenWidth$.value
.
The same is for mediaBreakpoint$
( or mediaBreakpoint$.value
)
import {
Injectable,
OnDestroy,
} from '@angular/core';
import {
Subject,
BehaviorSubject,
fromEvent,
} from 'rxjs';
import {
takeUntil,
debounceTime,
} from 'rxjs/operators';
@Injectable()
export class ResponsiveService implements OnDestroy {
private _unsubscriber$: Subject<any> = new Subject();
public screenWidth$: BehaviorSubject<number> = new BehaviorSubject(null);
public mediaBreakpoint$: BehaviorSubject<string> = new BehaviorSubject(null);
constructor() {
this.init();
}
init() {
this._setScreenWidth(window.innerWidth);
this._setMediaBreakpoint(window.innerWidth);
fromEvent(window, 'resize')
.pipe(
debounceTime(1000),
takeUntil(this._unsubscriber$)
).subscribe((evt: any) => {
this._setScreenWidth(evt.target.innerWidth);
this._setMediaBreakpoint(evt.target.innerWidth);
});
}
ngOnDestroy() {
this._unsubscriber$.next();
this._unsubscriber$.complete();
}
private _setScreenWidth(width: number): void {
this.screenWidth$.next(width);
}
private _setMediaBreakpoint(width: number): void {
if (width < 576) {
this.mediaBreakpoint$.next('xs');
} else if (width >= 576 && width < 768) {
this.mediaBreakpoint$.next('sm');
} else if (width >= 768 && width < 992) {
this.mediaBreakpoint$.next('md');
} else if (width >= 992 && width < 1200) {
this.mediaBreakpoint$.next('lg');
} else if (width >= 1200 && width < 1600) {
this.mediaBreakpoint$.next('xl');
} else {
this.mediaBreakpoint$.next('xxl');
}
}
}
Hope this helps someone
Solution 4 - Html
If you'd like you components to remain easily testable you should wrap the global window object in an Angular Service:
import { Injectable } from '@angular/core';
@Injectable()
export class WindowService {
get windowRef() {
return window;
}
}
You can then inject it like any other service:
constructor(
private windowService: WindowService
) { }
And consume...
ngOnInit() {
const width= this.windowService.windowRef.innerWidth;
}
Solution 5 - Html
The documentation for Platform
width()
and height()
, it's stated that these methods use window.innerWidth
and window.innerHeight
respectively. But using the methods are preferred since the dimensions are cached values, which reduces the chance of multiple and expensive DOM reads.
import { Platform } from 'ionic-angular';
...
private width:number;
private height:number;
constructor(private platform: Platform){
platform.ready().then(() => {
this.width = platform.width();
this.height = platform.height();
});
}
Solution 6 - Html
The answer is very simple. write the below code
import { Component, OnInit, OnDestroy, Input } from "@angular/core";
// Import this, and write at the top of your .ts file
import { HostListener } from "@angular/core";
@Component({
selector: "app-login",
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit, OnDestroy {
// Declare height and width variables
scrHeight:any;
scrWidth:any;
@HostListener('window:resize', ['$event'])
getScreenSize(event?) {
this.scrHeight = window.innerHeight;
this.scrWidth = window.innerWidth;
console.log(this.scrHeight, this.scrWidth);
}
// Constructor
constructor() {
this.getScreenSize();
}
}
Solution 7 - Html
You may use the typescript getter method for this scenario. Like this
public get width() {
return window.innerWidth;
}
And use that in template like this:
<section [ngClass]="{ 'desktop-view': width >= 768, 'mobile-view': width < 768
}"></section>
You won't need any event handler to check for resizing/ of window, this method will check for size every time automatically.
Solution 8 - Html
you can use this https://github.com/ManuCutillas/ng2-responsive Hope it helps :-)
Solution 9 - Html
@HostListener("window:resize", [])
public onResize() {
this.detectScreenSize();
}
public ngAfterViewInit() {
this.detectScreenSize();
}
private detectScreenSize() {
const height = window.innerHeight;
const width = window.innerWidth;
}
Solution 10 - Html
Now i know that the question is originally referring to the Screen size so basically the width
and height
attributes, but for most people Breakpoints
are what really matter, therefore, and to make a global reusable solution, I would prefer using Angular
's BreakpointObserver
to handle this.
The following configuration is basically a service
with some functions
that can return
an Observable<BreakpointState>
and to be subscribed
wherever needed:
import { Injectable } from '@angular/core';
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ScreenService {
constructor(private observer: BreakpointObserver) {}
isBelowSm(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 575px)']);
}
isBelowMd(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 767px)']);
}
isBelowLg(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 991px)']);
}
isBelowXl(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 1199px)']);
}
}
The above code can be adjusted to deal with screen size the bootstrap
way (By changing max-width
into min-width
and adding 1px
for each value, and ofcourse to inverse function
s names.)
Now in the component class
, simply subscribing
to the observable
returned by any of the above functions would do.
i.e: app.component.ts
:
export class AppComponent implements AfterViewInit {
isBelowLg: boolean;
constructor(private screenService: ScreenService) {}
ngAfterViewInit(): void {
this.screenService.isBelowLg().subscribe((isBelowLg: BreakpointState) => {
this.isBelowLg = isBelowLg.matches;
});
}
}
Refer that using AfterViewInit
life cycle hook would save lot of trouble for when it comes to detectChanges()
after view was initialized.
EDIT:
As an alternative for AfterViewInit
it's the same, but additionally, you will need to use ChangeDetectorRef
to detectChanges()
, simply inject an instance in the subscribing component i.e: app.component.ts
like this:
constructor(
private screenService: ScreenService,
private cdref: ChangeDetectorRef
) {}
And afterwards, just a call for detectChanges()
would do:
this.cdref.detectChanges();