293
votes

I would like to perform some tasks based on the window re-size event (on load and dynamically).

Currently I have my DOM as follows:

<div id="Harbour">
    <div id="Port" (window:resize)="onResize($event)" >
        <router-outlet></router-outlet>
    </div>
</div>

The event correctly fires

export class AppComponent {
   onResize(event) {
        console.log(event);
    }
}

How do I retrieve the Width and Height from this event object?

Thanks.

18
Not really an Angular question. look at the window object. You can get it's innerHeight and innerWidth properties..Sasxa
@Sasxa is correct, you just need to do console.log(event.target.innerWidth )Pankaj Parkar
Thanks for the info Sasxa/Pankaj - I wasn't sure whether it was just a plain javascript thing or a Typescript thing or an Angular event thing. I'm climbing a very steep learning curve for myself here and appreciate your input.DanAbdn

18 Answers

642
votes
<div (window:resize)="onResize($event)"
onResize(event) {
  event.target.innerWidth;
}

or using the HostListener decorator:

@HostListener('window:resize', ['$event'])
onResize(event) {
  event.target.innerWidth;
}

Supported global targets are window, document, and body.

Until https://github.com/angular/angular/issues/13248 is implemented in Angular it is better for performance to subscribe to DOM events imperatively and use RXJS to reduce the amount of events as shown in some of the other answers.

81
votes

I know this was asked a long time ago, but there is a better way to do this now! I'm not sure if anyone will see this answer though. Obviously your imports:

import { fromEvent, Observable, Subscription } from "rxjs";

Then in your component:

resizeObservable$: Observable<Event>
resizeSubscription$: Subscription

ngOnInit() {
    this.resizeObservable$ = fromEvent(window, 'resize')
    this.resizeSubscription$ = this.resizeObservable$.subscribe( evt => {
      console.log('event: ', evt)
    })
}

Then be sure to unsubscribe on destroy!

ngOnDestroy() {
    this.resizeSubscription$.unsubscribe()
}
74
votes

@Günter's answer is correct. I just wanted to propose yet another method.

You could also add the host-binding inside the @Component()-decorator. You can put the event and desired function call in the host-metadata-property like so:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  host: {
    '(window:resize)': 'onResize($event)'
  }
})
export class AppComponent{
   onResize(event){
     event.target.innerWidth; // window width
   }
}
40
votes

The correct way to do this is to utilize the EventManager class to bind the event. This allows your code to work in alternative platforms, for example server side rendering with Angular Universal.

import { EventManager } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Injectable } from '@angular/core';

@Injectable()
export class ResizeService {

  get onResize$(): Observable<Window> {
    return this.resizeSubject.asObservable();
  }

  private resizeSubject: Subject<Window>;

  constructor(private eventManager: EventManager) {
    this.resizeSubject = new Subject();
    this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
  }

  private onResize(event: UIEvent) {
    this.resizeSubject.next(<Window>event.target);
  }
}

Usage in a component is as simple as adding this service as a provider to your app.module and then importing it in the constructor of a component.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'my-component',
  template: ``,
  styles: [``]
})
export class MyComponent implements OnInit {

  private resizeSubscription: Subscription;

  constructor(private resizeService: ResizeService) { }

  ngOnInit() {
    this.resizeSubscription = this.resizeService.onResize$
      .subscribe(size => console.log(size));
  }

  ngOnDestroy() {
    if (this.resizeSubscription) {
      this.resizeSubscription.unsubscribe();
    }
  }
}
32
votes

Here is a better way to do it. Based on Birowsky's answer.

Step 1: Create an angular service with RxJS Observables.

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';

@Injectable()
export class WindowService {
    height$: Observable<number>;
    //create more Observables as and when needed for various properties
    hello: string = "Hello";
    constructor() {
        let windowSize$ = new BehaviorSubject(getWindowSize());

        this.height$ = (windowSize$.pluck('height') as Observable<number>).distinctUntilChanged();

        Observable.fromEvent(window, 'resize')
            .map(getWindowSize)
            .subscribe(windowSize$);
    }

}

function getWindowSize() {
    return {
        height: window.innerHeight
        //you can sense other parameters here
    };
};

Step 2: Inject the above service and subscribe to any of the Observables created within the service wherever you would like to receive the window resize event.

import { Component } from '@angular/core';
//import service
import { WindowService } from '../Services/window.service';

@Component({
    selector: 'pm-app',
    templateUrl: './componentTemplates/app.component.html',
    providers: [WindowService]
})
export class AppComponent { 

    constructor(private windowService: WindowService) {

        //subscribe to the window resize event
        windowService.height$.subscribe((value:any) => {
            //Do whatever you want with the value.
            //You can also subscribe to other observables of the service
        });
    }

}

A sound understanding of Reactive Programming will always help in overcoming difficult problems. Hope this helps someone.

13
votes

I haven't seen anyone talking about MediaMatcher of angular/cdk.

You can define a MediaQuery and attach a listener to it - then anywhere on your template (or ts) you can invoke stuff if the Matcher is matched. LiveExample

App.Component.ts

import {Component, ChangeDetectorRef} from '@angular/core';
import {MediaMatcher} from '@angular/cdk/layout';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  mobileQuery: MediaQueryList;

  constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher) {
    this.mobileQuery = media.matchMedia('(max-width: 600px)');
    this._mobileQueryListener = () => changeDetectorRef.detectChanges();
    this.mobileQuery.addListener(this._mobileQueryListener);
  }

  private _mobileQueryListener: () => void;

  ngOnDestroy() {
    this.mobileQuery.removeListener(this._mobileQueryListener);
  }

}

App.Component.Html

<div [class]="mobileQuery.matches ? 'text-red' : 'text-blue'"> I turn red on mobile mode 
</div>

App.Component.css

.text-red { 
   color: red;
}

.text-blue {
   color: blue;
}

source: https://material.angular.io/components/sidenav/overview

12
votes

There's a ViewportRuler service in angular CDK. It runs outside of the zone, and works with server side rendering too.

6
votes

Assuming that < 600px means mobile to you, you can use this observable and subscribe to it:

First we need the current window size. So we create an observable which only emits a single value: the current window size.

initial$ = Observable.of(window.innerWidth > 599 ? false : true);

Then we need to create another observable, so that we know when the window size was changed. For this we can use the "fromEvent" operator. To learn more about rxjs`s operators please visit: rxjs

resize$ = Observable.fromEvent(window, 'resize').map((event: any) => {
  return event.target.innerWidth > 599 ? false : true;
 });

Merg these two streams to receive our observable:

mobile$ = Observable.merge(this.resize$, this.initial$).distinctUntilChanged();

Now you can subscribe to it like this:

mobile$.subscribe((event) => { console.log(event); });

Remember to unsubscribe :)

3
votes

Based on the solution of @cgatian I would suggest the following simplification:

import { EventManager } from '@angular/platform-browser';
import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class ResizeService {

  public onResize$ = new EventEmitter<{ width: number; height: number; }>();

  constructor(eventManager: EventManager) {
    eventManager.addGlobalEventListener('window', 'resize',
      e => this.onResize$.emit({
        width: e.target.innerWidth,
        height: e.target.innerHeight
      }));
  }
}

Usage:

import { Component } from '@angular/core';
import { ResizeService } from './resize-service';

@Component({
  selector: 'my-component',
  template: `{{ rs.onResize$ | async | json }}`
})
export class MyComponent {
  constructor(private rs: ResizeService) { }
}
3
votes

This is not exactly answer for the question but it can help somebody who needs to detect size changes on any element.

I have created a library that adds resized event to any element - Angular Resize Event.

It internally uses ResizeSensor from CSS Element Queries.

Example usage

HTML

<div (resized)="onResized($event)"></div>

TypeScript

@Component({...})
class MyComponent {
  width: number;
  height: number;

  onResized(event: ResizedEvent): void {
    this.width = event.newWidth;
    this.height = event.newHeight;
  }
}
2
votes

I wrote this lib to find once component boundary size change (resize) in Angular, may this help other people. You may put it on the root component, will do the same thing as window resize.

Step 1: Import the module

import { BoundSensorModule } from 'angular-bound-sensor';

@NgModule({
  (...)
  imports: [
    BoundSensorModule,
  ],
})
export class AppModule { }

Step 2: Add the directive like below

<simple-component boundSensor></simple-component>

Step 3: Receive the boundary size details

import { HostListener } from '@angular/core';

@Component({
  selector: 'simple-component'
  (...)
})
class SimpleComponent {
  @HostListener('resize', ['$event'])
  onResize(event) {
    console.log(event.detail);
  }
}
2
votes

Below code lets observe any size change for any given div in Angular.

<div #observed-div>
</div>

then in the Component:

oldWidth = 0;
oldHeight = 0;

@ViewChild('observed-div') myDiv: ElementRef;
ngAfterViewChecked() {
  const newWidth = this.myDiv.nativeElement.offsetWidth;
  const newHeight = this.myDiv.nativeElement.offsetHeight;
  if (this.oldWidth !== newWidth || this.oldHeight !== newHeight)
    console.log('resized!');

  this.oldWidth = newWidth;
  this.oldHeight = newHeight;
}
2
votes

I checked most of these answers. then decided to check out Angular documentation on Layout.

Angular has its own Observer for detecting different sizes and it is easy to implement into the component or a Service.

a simpl example would be:

import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';

@Component({...})
class MyComponent {
  constructor(breakpointObserver: BreakpointObserver) {
    breakpointObserver.observe([
      Breakpoints.HandsetLandscape,
      Breakpoints.HandsetPortrait
    ]).subscribe(result => {
      if (result.matches) {
        this.activateHandsetLayout();
      }
    });
  }
}

hope it helps

1
votes

On Angular2 (2.1.0) I use ngZone to capture the screen change event.

Take a look on the example:

import { Component, NgZone } from '@angular/core';//import ngZone library
...
//capture screen changed inside constructor
constructor(private ngZone: NgZone) {
    window.onresize = (e) =>
    {
        ngZone.run(() => {
            console.log(window.innerWidth);
            console.log(window.innerHeight);
        });
    };
}

I hope this help!

1
votes

Here is an update to @GiridharKamik answer above with the latest version of Rxjs.

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, fromEvent } from 'rxjs';
import { pluck, distinctUntilChanged, map } from 'rxjs/operators';

@Injectable()
export class WindowService {
    height$: Observable<number>;
    constructor() {
        const windowSize$ = new BehaviorSubject(getWindowSize());

        this.height$ = windowSize$.pipe(pluck('height'), distinctUntilChanged());

        fromEvent(window, 'resize').pipe(map(getWindowSize))
            .subscribe(windowSize$);
    }

}

function getWindowSize() {
    return {
        height: window.innerHeight
        //you can sense other parameters here
    };
};
0
votes

all the solutions are not working with server-side or in angular universal

0
votes

Here is a simple and clean solution I created so I could inject it into multiple components.

ResizeService.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ResizeService {

  constructor() {

    window.addEventListener('resize', (e) => {
      this.onResize.next();
    });

  }

  public onResize = new Subject();

}

In use:

constructor(
  private resizeService: ResizeService
) { 

  this.subscriptions.push(this.resizeService.onResize.subscribe(() => {
    // Do stuff
  }));

}

private subscriptions: Subscription[] = [];
0
votes

What I did is as follows, much like what Johannes Hoppe suggested:

import { EventManager } from '@angular/platform-browser';
import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class ResizeService {

  public onResize$ = new EventEmitter<{ width: number; height: number; }>();

  constructor(eventManager: EventManager) {
    eventManager.addGlobalEventListener('window', 'resize',
      event => this.onResize$.emit({
        width: event.target.innerWidth,
        height: event.target.innerHeight
      }));
  }
  
  getWindowSize(){
    this.onResize$.emit({
    width: window.innerWidth,
    height: window.innerHeight
    });
  }
}

In app.component.ts:

Import { ResizeService } from ".shared/services/resize.service"
import { Component } from "@angular/core"

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent{
  windowSize: {width: number, height: number};
  
  constructor(private resizeService: ResizeService){
  }

  ngOnInit(){
    this.resizeService.onResize$.subscribe((value) => {
      this.windowSize = value;
    });
    
    this.resizeService.getWindowSize();
  }
}

Then in your app.component.html:

<router-outlet *ngIf = "windowSize?.width > 1280 && windowSize?.height > 700; else errorComponent">
</router-outlet>

<ng-template #errorComponent>
  <app-error-component></app-error-component>
</ng-template>