问题
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
回答1:
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.)
回答2:
Since my goal was to be able to abstract the window size streams in a different module, apparently just wrapping the streams in a class sealed the deal:
"This is the future" version:
import {Observable, BehaviorSubject} from 'rxjs';
export class WindowSize {
width$: Observable<number>;
height$: Observable<number>;
constructor() {
let windowSize$ = createWindowSize$();
this.width$ = (windowSize$.pluck('width') as Observable<number>).distinctUntilChanged();
this.height$ = (windowSize$.pluck('height') as Observable<number>).distinctUntilChanged();
}
}
const createWindowSize$ = () =>
Observable.fromEvent(window, 'resize')
.map(getWindowSize)
.startWith(getWindowSize())
.publishReplay(1)
.refCount();
const getWindowSize = () => {
return {
height: window.innerHeight,
width: window.innerWidth
}
};
"Granny" version:
import {Observable, BehaviorSubject} from 'rxjs';
export class WindowSize {
width$: Observable<number>;
height$: Observable<number>;
constructor() {
let windowSize$ = new BehaviorSubject(getWindowSize());
this.width$ = (windowSize$.pluck('width') as Observable<number>).distinctUntilChanged();
this.height$ = (windowSize$.pluck('height') as Observable<number>).distinctUntilChanged();
Observable.fromEvent(window, 'resize')
.map(getWindowSize)
.subscribe(windowSize$);
}
}
function getWindowSize() {
return {
height: window.innerHeight,
width: window.innerWidth
};
}
Although I didn't want class/service in this module, just clear/platform-independent constructs, this was the only clean way that worked for angular without needing to care about triggering zone updates.
来源:https://stackoverflow.com/questions/35513015/async-pipe-not-rendering-the-stream-updates