How to handle change detection when I use runOutsideAngular fromEvent?

Viewed 287

In my angular application I listen to resize event and update the breakpoint name in breakpointChanged subject:

 breakpointChanged = new Subject();

  resize$ = fromEvent(window, "resize").pipe(
    startWith(() => []),
    map(() => [window.innerHeight, window.innerWidth]),
    tap(([height, width]) => {
      const name = this.breakpoint(width);

      this.breakpointChanged.next(name);
    })
  );

Base on the breakpoint name I decided when the drawer should be open or not:

drawerOpened$ = this.breakpointChanged.pipe(
    distinctUntilChanged(),
    map((breakpointName) => {
      return breakpointName !== "xs"; // if it's not xs breakpoint then it's true mean it's open.
    })
  );

I also want to use distinctUntilChanged because if the breakpoint not changed I don't want to activate the stream.

And in ngOnInit I subscribe to the resize stream. And I do it in runOutsideAngular, so I wont trigger change detection every time the resize happens.

 this.zone.runOutsideAngular(() => {
   this.resize$.subscribe();
 });

The problem is the view doesn't update.

Only when I wrap the this.breakpointChanged.next(name); with this.zone.run(() => {}) it's works but if I do that I'll trigger change detection - always when the resize happens. which is bad thing.

Any idea how to solve this problem and make it nice and clean?

In the same context, how async pipe doesn't make change detection? the value is changing and async should subscribe and trigger when it's the value changes.

codesandbox.io

2 Answers

That's a nice question. May be we can reduce the complexity by using debounceTime() as below:


  resize$ = fromEvent(window, "resize").pipe(
    startWith(() => []),
    debounceTime(1000),
    map(() => [window.innerHeight, window.innerWidth]),
    tap(([height, width]) => {
      console.log(height);
      const name = this.breakpoint(width);
      this.breakpointChanged.next(name);
    })
  );

and removing this.zone.runOutsideAngular

  ngOnInit() {
      this.resize$.subscribe();
  }

no matter how many times it is resized, the event will be emitted after 1 second.

Here is what I tried in your example.

Let me know if this helps

  • Add a debounceTime(250) as the first param of the fromEvent(...).pipe(...)

  • Wrap the breakpoint.next in the zone.run(..) just as you were doing, and also keep the same runOutsideAngular approach

Change detection will run after every 250ms duration as you resize, which is what you're after - a good user experience plus limited/less change detection

Related