Jon Sud
Jon Sud

Reputation: 11641

How to handle change detection when I use runOutsideAngular fromEvent?

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

Upvotes: 2

Views: 710

Answers (2)

Drenai
Drenai

Reputation: 12357

  • 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

Upvotes: 0

Shashank Vivek
Shashank Vivek

Reputation: 17504

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

Upvotes: 1

Related Questions