4

I'm trying to create a Angular service that components can use to watch the window resize event. After lots of attempts I finally found this solution worked best https://stackoverflow.com/a/43833815.

However, it seemed to cause an error when running in SSR.

TypeError: this.eventManager.addGlobalEventListener is not a function

After lots of attempts this is where I'm at:

Service

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

@Injectable()
export class WindowService {
  private resizeSubject: Subject<Window>;

  constructor(
    @Inject(PLATFORM_ID) private eventManager: EventManager) { // Event is not defined without @Inject(PLATFORM_ID) 
      this.resizeSubject = new Subject();

      this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
  }

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

  /**
   * Get an observerable for the window resize event
   * @returns   Return the window resize as an observable
   */
  get onResize$(): Observable<Window> {
    return this.resizeSubject.asObservable();
  }
}

Component

import { Component, ElementRef, Input, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs/Subscription';

import { WindowService } from '../../services/window.service';


@Component({
  selector: 'app-sidebar',
  templateUrl: './sidebar.component.html'
})

export class SidebarComponent implements OnInit, OnDestroy {
  element: HTMLElement;
  resizeSubscription: Subscription;

  constructor(
    private readonly el: ElementRef,
    private windowService: WindowService) {
      this.element = el.nativeElement;
  }

  ngOnInit(): void {
    const self = this;

    this.resizeSubscription = this.windowService.onResize$
      .debounceTime(100)
      .subscribe(function(windowObj) {
        if (windowObj.innerWidth >= 1024) {
          self.open();
        } else {
          self.close();
        }
      });
  }

  ngOnDestroy(): void {
    if (this.resizeSubscription) {
      this.resizeSubscription.unsubscribe();
    }
  }

  ...
}

It seems a very complex way to bind to the window resize event but I've not found a better way that works with Angular SRR. Is there a recommended approach for this? Essentially I need to check the window size on resize (and also on load) and open or close the sidebar accordingly.

6
  • Is there something preventing you to use the following syntax in your service window.onresize = ... ? Commented Jul 4, 2018 at 17:23
  • @Ploppy I'm not sure that will work as Angular SSR won't know what "window" is? Commented Jul 4, 2018 at 17:26
  • I did not read carefully and missed the SSR part, but then, I think you are doing alright, you even debounce the observable. I don't see anything bad but wait for some more people to confirm. Commented Jul 4, 2018 at 17:35
  • 1
    Why don't you check the platform is to only add the event handler if you are client side? Commented Jul 4, 2018 at 18:17
  • @David I tried wrapping the this.eventManager.addGlobalEventListener in an isPlatform check which then loads server-side fine but when it transfers state to client-side it errors. I assume because at that point the WindowService constructor has already initialised and angular doesn't re-initialise the globalEventListener. Commented Jul 5, 2018 at 7:28

2 Answers 2

1
<div (window:resize)="onResize($event)"

Method:

onResize(event) {
  event.target.innerWidth;
}

or

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

Supported global targets are window, document, and body.

Sign up to request clarification or add additional context in comments.

3 Comments

Will this work SSR? If several components take this approach will there be a performance hit as the window resize is not being handled through a service?
Also, how would you debounce this?
@lawlesscreation There will be a performance hit for sure because it will trigger a change detection with every time the event emits, the resize and scroll events should run outside angular zone. HostListener is good for events like click were triggering a change detection make sense.
1

You can use platformId for write different code server and browser:

 constructor( @Inject(PLATFORM_ID) private platformId: Object, private eventManager: EventManager) {
      this.resizeSubject = new ReplaySubject();
    if (isPlatformBrowser(this.platformId)) {
      this.eventManager.addGlobalEventListener('window', 'resize', this.onResize.bind(this));
    }
  }

For user global window, document, event write like: https://github.com/Angular-RU/angular-universal-starter/blob/6d1e980655a55b2e1651ac5d040aaff80bc8fe8c/server.ts#L13

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.