“async”管道不呈现流更新

时间:2016-02-19 19:05:11

标签: angular rxjs rxjs5 angular2-changedetection

尝试使用async管道通过角度为2的组件中的流来调整窗口上的窗口大小:

<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() {  }
}

但该组件仅呈现初始状态并忽略流更新。 如果您打开控制台,在窗口调整大小时,您将看到来自同一个流的更新。

无法理解我在这里缺少的东西。

这是plunker

2 个答案:

答案 0 :(得分:14)

由于我的目标是能够在不同的模块中抽象窗口大小流,显然只是在一个类中包装流密封了交易:

“这是未来”版本:

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
  }
};

“奶奶”版本:

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
    };
}

虽然我不想在这个模块中使用类/服务,只是清除/平台无关的构造,但这是唯一一种适用于角度的干净方式,无需关心触发区域更新。

答案 1 :(得分:10)

事件处理程序在Angular区域外运行,因此在事件触发时不会运行Angular更改检测。将事件处理程序放在组件中,然后它将与所有其他异步事件一起进行猴子修补,因此Angular更改检测将在每个事件之后执行(并更新视图):

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

Plunker

评论中讨论的另一个选项是在更新视图模型时manually run change detection

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

请注意,您可能希望尝试运行ApplicationRef.tick()一次,例如在根组件中运行,该组件将对所有组件运行更改检测 - 而不是在每个组件中运行ChangeDetectorRef.detectChanges()。 (并且您可能需要在tick()方法中包含setTimeout(),以确保所有组件视图模型都已更新...我不确定所有do()回调的时间方法将被执行 - 即,如果它们都在JavaScript VM的一个回合中运行,或者涉及多个回合。)