197

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.

pjpscriv
  • 866
  • 11
  • 20
Ronit Oommen
  • 3,040
  • 4
  • 17
  • 25

10 Answers10

436

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;
}
Eduardo Vargas
  • 8,752
  • 2
  • 20
  • 27
  • 7
    this.innerWidth = event.target.innerWidth; ... is possibly more efficient, produces the same result – danday74 Nov 12 '18 at 08:47
  • 3
    Also recommend this in the constructor if you are using lodash ... this.onResize = debounce(this.onResize, 150, {leading: false, trailing: true}) ... to prevent the onResize method being called too frequently ... you will need to ... import { debounce } from 'lodash' – danday74 Nov 12 '18 at 08:53
  • I think preferred to use `ngAfterViewInit` life hook method instead of `ngOnInit` life hook method – ahmed hamdy May 27 '20 at 15:41
  • The @HostListener code can come within an angular component (e.g., under the constructor. The ['$event'] and event parameter can be skipped, although the innerWidth can also be found in that parameter, which would make the function more testable. To make the code more DRY, you can call onResize from within ngOnInit or ngAfterViewInit, but then you have to deal with the event parameter. – Marcus Oct 07 '21 at 20:08
  • Hi, where to add the second code snippet? – Arj 1411 Nov 17 '21 at 06:36
  • I added this to my app.component and it worked fine (with lodash): @HostListener('window:resize', ['$event']) onResize(event) { doYourStuff(); this.onResize = debounce(this.onResize, 150, { leading: false, trailing: true, }); } – Stef Mar 20 '22 at 20:06
75

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
      }
    });
  }
}
xinthose
  • 3,213
  • 3
  • 40
  • 59
dannybucks
  • 2,217
  • 2
  • 18
  • 24
  • if you want to act only if the observer matches criteria you got to add inside subscribe this `if (result.matches)` otherwise it will call even if it don't – karoluS Dec 27 '18 at 10:47
  • 1
    @karoluS: Yes of course. I edited my answer to make that clear. Thank you. – dannybucks Jan 14 '19 at 07:24
  • Seems that this `cdk` has peer dependency on specific version of angular. Like 7 for the latest. Anyway I can use this for older version(angular 4 as in the question)? – LeOn - Han Li Jan 15 '19 at 00:00
  • @Leon li: I actually use angular 7, that's right. However: As far as I know you can use cdk in angular 4 too. According to [this blog post](https://www.tutorialspoint.com/angular4/angular4_materials.htm) you need cdk 2.x. As far as I know, this has to be installed manually and with the version specified like this: npm @angular/cdk@4 – dannybucks Jan 30 '19 at 07:15
  • @JeremyBenks Y, we are using ng `4.2.6` specifically. Cannot find a cdk 2.x verison on that, only 4.1.x and 4.3.x. Anyway, Thanks! – LeOn - Han Li Jan 30 '19 at 15:01
  • @Leon li: according to [npm (in versions)](https://www.npmjs.com/package/@angular/cdk) there is a version 2.0.0-beta.12. But I found that this is not found with a normal npm install @angular/cdk@2, you have to specify the full version e.g. npm install @angular/cdk@2.0.0-beta.12. But you might not want to use a beta version.. Well, maybe update angular soon? ;-) – dannybucks Jan 31 '19 at 07:12
19

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

Stephen Wight
  • 67
  • 1
  • 7
Denis R
  • 266
  • 2
  • 3
  • My favourite answer! Just make sure you add this.init() in the constructor for it to work. – Ben Apr 10 '20 at 07:31
13

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;
  }
Kildareflare
  • 4,590
  • 5
  • 51
  • 65
  • 5
    I can't see the point on doing a service to do exactly what it's already at hand. window.innerWidth. – Rodrigo Jun 25 '19 at 22:09
  • 7
    Firstly DI is the Angular way and makes the dependencies of a component / directive clearer. But the main point mentioned here is to make the code that uses the service easier to test. Using this approach the WindowService can be mocked in any test written for components that use the service. – Kildareflare Jul 07 '19 at 22:15
11

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();
    });
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
robbannn
  • 5,001
  • 1
  • 32
  • 47
9

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();
}
}
Harish Mahajan
  • 3,254
  • 4
  • 27
  • 49
9

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.

Rohan Gayen
  • 375
  • 1
  • 4
  • 11
7

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 functions 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();
Mohamed Karkotly
  • 1,364
  • 3
  • 13
  • 26
6
@HostListener("window:resize", [])
public onResize() {
  this.detectScreenSize();
}

public ngAfterViewInit() {
    this.detectScreenSize();
}

private detectScreenSize() {
    const height = window.innerHeight;
    const width = window.innerWidth;
}
Dhruv Parekh
  • 316
  • 3
  • 6
5

you can use this https://github.com/ManuCutillas/ng2-responsive Hope it helps :-)