1

I met a very interesting in my angular2 application, I simplify the situation here.

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

 }

And this is template

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

I push a new tab to tabs when Worker done. And angular2 won't do change detect after Worker onmessage callback, and I must trigger change detection manually.

But after that, the background of list in the added tab won't change after click(the callback function was executed, so it is that the change detection does not work).

And if I do something else like click the Add Tab button again, the change detection will work and set the list background as expect.

And if I do not trigger change detection manually. After pushing tab, do something else(eg. click button again) to trigger change detection. And now the list will show, and the background change works well.

I know the key is manually call change detection after Worker execute, but I don't know what happened in detail cause the list background change detection do not work.

CozyAzure
  • 8,280
  • 7
  • 34
  • 52
Micarle
  • 131
  • 9

2 Answers2

2

You are invoking a new worker everytime you call the add tab function.

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

Instead, Declare the worker on top as scope bound variable to this Class & use it in the function.

 worker : any = new Worker("worker.js");
    addTab() {
      this.worker.postMessage("");
      this.worker.onmessage = (event) => {
      this.tabs.push({
         label: 'label'
      })
Bala Abhinav
  • 1,328
  • 1
  • 9
  • 15
1

NgZone patches async APIs like setTimeout or addEventListener. When the callback of such an APIs is called, Angular recognizes it and runs change detection.

Obviously worker.onmessage is not covered by NgZone, therefore change detection is not invoked.

There are different ways to invoke change detection Triggering Angular2 change detection manually

detectChanges() only invokes change detection for the current element. If the model change has wider effects (outside your component), then you might need to run application wide change detection (for example if you rn router.navigate() from a callback that runs outside Angulars zone (not covered by NgZone. In such cases you can make the code run inside the zone like

ngZone.run(_ => { router.navigate(); }
Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Could it be also that even if OP is pushing something to `this.tabs`, since the array reference doesn't change, the change detection doesn't recognize any changes? – eko Jan 20 '17 at 07:40
  • 1
    He uses `tabs` with `ngFor` and `ngFor` does it's own change detection https://github.com/angular/angular/blob/d4d3782d455a484e8aa26ec9a57ee2b4727bc1da/modules/%40angular/common/src/directives/ng_for.ts#L136-L141 but a good thought anyways :) – Günter Zöchbauer Jan 20 '17 at 07:42
  • I have tried three methods to manually trigger change detection,but ApplicationRef.tick() and ChangeDetectorRef.detectChanges() do not work well, I mean the tabs changes can be apply. but when I click list in a tab, it's background color do not change until I do something else(eg. click button again) to trigger the change detection. I know that angular create change detect code for every component, and I guess here the ApplicationRef and ChangeDetectorRef only do detect, and NgZone will create change detection code for list. what do you think about that? THANK YOU @GünterZöchbauer – Micarle Jan 20 '17 at 07:55
  • `ApplicationRef.tick()` invokes change detection on the root node, `ChangeDetecorRef.detectChanges()` invokes change detection on the current component. `zone.run(...)` runs change detection on the root node after the passed callback has been completed. `zone.run()` doesn't create some kind of change detector. `NgZone` is Angulars change detector and it's always present - it's just that code can run outside its context so `NgZone` doesn't know about the code being run. – Günter Zöchbauer Jan 20 '17 at 07:59
  • Thanks for clarification, and I still do not understand why the list background change can not be detected when I use first two methods. – Micarle Jan 20 '17 at 08:08
  • Hard to tell. I would also expect it to work. Can you try to reproduce in a Plunker? – Günter Zöchbauer Jan 20 '17 at 08:11
  • This is [plunker](https://plnkr.co/edit/YirqqHDMUiqvA6zyGxE3?p=preview) – Micarle Jan 20 '17 at 08:49
  • Thanks a lot for the plunker. It's really weird. When I add `detectChanges()` to `selectViewMode()` https://plnkr.co/edit/HgWdUzp51vszGp3imzBq?p=preview, then it's working as well, but code invoked from an event binding never runs outside Angulars zone. Looks like a bug to me. – Günter Zöchbauer Jan 20 '17 at 08:57