【问题标题】:"async" pipe not rendering the stream updates“异步”管道不呈现流更新
【发布时间】:2016-06-01 11:33:04
【问题描述】:

尝试使用 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

【问题讨论】:

    标签: angular rxjs rxjs5 angular2-changedetection


    【解决方案1】:

    @Marks 解决方案对我不起作用,起作用的方法类似于以下 hack:

    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
         setTimeout(()=>this._cdr.detectChanges())
    });  
    

    但后来我真的想到了 Mark 的注释“事件处理程序在 Angular 区域之外运行”,并意识到在我的用例中,我使用的是 javascript 的 firestore 而不是 angular 的 firestore,这使得在 diffrenet 事件处理程序上可以观察到快照更改。切换回 angular 的 firestore 后 - 它起作用了!

    【讨论】:

      【解决方案2】:

      因为我的目标是能够在不同的模块中抽象窗口大小的流,显然只是将流包装在一个类中就可以了:

      “这就是未来”版本:

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

      虽然我不想在这个模块中使用类/服务,只需要清晰/独立于平台的构造,但这是唯一适用于 Angular 而无需关心触发区域更新的简洁方式。

      【讨论】:

      • 不错的解决方案。我很好奇,需要@Injectable() 才能让它工作吗?
      • @MarkRajcok 是必需的,因为他已将其作为服务添加到引导数组中......
      • @PankajParkar,如果一个服务不依赖于其他服务,则不需要@Injectable() 装饰器(即使你将它放在bootstrap() 数组中)。所以我的问题仍然存在。我仍然想知道/询问这个用例是否有任何不同。我的直觉是这里不需要@Injectable。 (但是,我知道最好始终包含 @Injectable 装饰器。)
      • @MarkRajcok 抱歉,我认为这是推断的:现在我将其作为服务,其余组件确实依赖于此,我确实通过依赖注入将其拉出,这就是使这些流在角度执行上下文(区域)中运行。所以现在它在大区!
      • @MarkRajcok 你应该把钱放在上面。它可以在没有@Injectable() 的情况下工作
      【解决方案3】:

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

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

      Plunker


      在 cmets 中讨论的另一个选项是在更新视图模型时发送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 的一个回合中运行,或者如果涉及多个回合。)

      【讨论】:

      • 呵呵,这绝对解决了。读取的小区域已过期.. 但在我的实现中,我想在不同的文件中抽象出大小流(无组件,无 ngOnInit),并为所有组件公开映射的 height$ 和 width$ 流,以便根据需要附加。我如何让区域知道这些流更新?
      • @Birowsky,一种方法是在您进行视图模型更改后手动运行更改检测:stackoverflow.com/a/35106069/215945,这是一个显示:plnkr.co/edit/eiua3K9aRRFEyxV3vbtP?p=preview
      猜你喜欢
      • 2021-10-12
      • 2019-08-02
      • 2018-06-26
      • 2013-07-11
      • 2020-09-30
      • 2014-12-05
      • 1970-01-01
      • 1970-01-01
      • 2019-09-22
      相关资源
      最近更新 更多