Worker.onmessage 后的 angular2 变化检测无法按预期工作

angular2 change detection after Worker.onmessage does not work as expect

我在我的angular2应用中遇到一个很有意思的,我这里简化一下情况。

这是 AppComponnet

export class AppComponent {

    tabs: any = [];

    viewModes = [
       { label: "列表查看"},
       { label: "树状图" },
    ];


   constructor(private changeRef: ChangeDetectorRef) {}

   addTab() {
      var worker = new Worker("worker.js");
      worker.postMessage("");
      worker.onmessage = (event) => {
      this.tabs.push({
         label: 'label'
      })
     this.changeRef.detectChanges();
    }
  }

  currentMode: any;
  selectViewMode(mode: any) {
      if (this.currentMode) {
         this.currentMode.selected = false;
      }
      mode.selected = true;
      this.currentMode = mode;
   }

 }

这是模板

    <div *ngFor="let tab of tabs">
        <ul>
           <li *ngFor="let item of viewModes">
                <a (click)="selectViewMode(item)"     [style.background]="item.selected ? '#f69c55' : ''"> {{item.label}} </a>
           </li>
        </ul>
     </div>

     <button (click)="addTab()">Add Tab</button>

我在 Worker 完成后将一个新标签推送到标签。而且angular2不会在Worker onmessage回调后做变化检测,我必须手动触发变化检测。

但是之后,添加的tab中的list的背景在点击后不会发生变化(回调函数被执行了,所以变化检测没有起作用)。

如果我再次单击“添加选项卡”按钮等其他操作,更改检测将起作用并按预期设置列表背景。

如果我不手动触发更改检测。按下选项卡后,执行其他操作(例如再次单击按钮)以触发更改检测。现在列表会显示出来了,背景变化效果很好。

我知道关键是在Worker执行后手动调用变化检测,但我不知道具体发生了什么导致列表后台变化检测不起作用。

NgZone 修补异步 API,如 setTimeoutaddEventListener。当调用此类 API 的回调时,Angular 会识别它并 运行 进行更改检测。

显然 worker.onmessage 未包含在 NgZone 中,因此未调用更改检测。

调用变化检测的方法有多种

detectChanges() 仅调用当前元素的更改检测。 如果模型更改具有更广泛的影响(在您的组件之外),那么您可能需要 运行 应用程序范围的更改检测(例如,如果您从 运行 外部的回调中 rn router.navigate() Angulars 区域(未被 NgZone 覆盖。 在这种情况下,您可以将区域内的代码 运行 设为

ngZone.run(_ => { router.navigate(); }

每次调用添加选项卡功能时,您都在调用一个新工作器。

addTab() {
      var worker = new Worker("worker.js");

相反,将 worker on top 声明为此 Class 的范围绑定变量并在函数中使用它。

 worker : any = new Worker("worker.js");
    addTab() {
      this.worker.postMessage("");
      this.worker.onmessage = (event) => {
      this.tabs.push({
         label: 'label'
      })