Search code examples
angularrxjsrxjs5angular2-changedetection

"async" pipe not rendering the stream updates


Trying to render the window size on window resize through a stream in an angular 2 component utilizing an async pipe:

<h2>Size: {{size$ | async | json}}</h2>

const windowSize$ = new BehaviorSubject(getWindowSize());
Observable.fromEvent(window, 'resize')
  .map(getWindowSize)
  .subscribe(windowSize$);

function getWindowSize() {
  return {
    height: window.innerHeight,
    width: window.innerWidth
  };
}

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Size: {{size$ | async | json}}</h2>
    </div>
  `,
  directives: []
})
export class App {
  size$ = windowSize$.do(o => console.log('size:', o));
  constructor() {  }
}

But the component only renders the initial state and ignores the stream updates. If you open the console, on window resize, you'll see the updates from that same stream.

Can't understand what i am missing here.

Here's a plunker


Solution

  • The event handler is running outside the Angular zone, so Angular change detection doesn't run when an event fires. Put the event handler inside your component and then it will get monkey-patched along with all of the other asynchronous events, hence Angular change detection will execute after each event (and update the view):

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

    Plunker


    Another option, discussed in the comments, is to manually run change detection when a view model is updated:

    import {Component, ChangeDetectorRef} from 'angular2/core'
    ...
    export class App {
      size$ = windowSize$.do(o => {
         console.log('size:', o);
         // since the resize event was not registered while inside the Angular zone,
         // we need to manually run change detection so that the view will update
         this._cdr.detectChanges();
      });
    
      constructor(private _cdr: ChangeDetectorRef) {}
    }
    

    Plunker

    Note that you might instead want to try running ApplicationRef.tick() once, say in your root component, which will run change detection on all of the components – rather than running ChangeDetectorRef.detectChanges() in each component. (And you might need to wrap tick() inside a setTimeout() method, to ensure that all of the component view models were updated... I'm not sure when all of the do() callback methods will be executed -- i.e., if they all run in one turn of the JavaScript VM, or if multiple turns are involved.)