Search code examples
javascriptangularangular8angular-lifecycle-hooks

Angular not detecting changes in ngFor in a new window from a postMessage


Background: my main page opens up an external window (same origin) of another module within my project upon button click. I also set up a BroadcastChannel so that these two windows can now communicate. Now, if this window is already open & the user clicks the triggering button once again, I want to communicate this to the window:

onAddNewFieldClick() {
    if (this.window === null) {
      this.window = window.open(window.location.origin + '/wizard', 'Field Wizard', 'resizable,scrollbar');
      this.channel = new BroadcastChannel('edit-spec-wizard-channel');
    } else {
      this.channel.postMessage(1);
    }
  }

The new window listens on this channel and appends the message data to an array that is used in an ngFor. To be extra safe. I go ahead and create a brand new array each time a new value is pushed to cause a rebind. Here is the logic that powers the component in the new window.

export class EntryComponent implements OnInit, OnDestroy {

  newFieldChannel: BroadcastChannel;
  newFields: number[] = [];
  constructor() { }

  ngOnInit() {
    this.newFieldChannel = new BroadcastChannel('edit-spec-wizard-channel');
    this.newFieldChannel.onmessage = this.newFieldChannelOnMessage.bind(this);
    this.newFields.push(1);
  }

  func() {
    this.newFields.push(1);
    this.newFields = this.newFields.slice();
  }

  private newFieldChannelOnMessage(event: MessageEvent) {
    this.newFields.push(event.data as number);
    this.newFields = this.newFields.slice();
  }

  ngOnDestroy() {
    this.newFieldChannel.close();
  }
}

And here is the template HTML:

<div class="row">
  <div class="col" *ngFor="let newField of newFields">
    <div style="width: 300px; height: 600px; background-color: white;">
      NEW FIELD BOX
    </div>
  </div>
  <button class="btn btn-primary" (click)="func()">Click me</button>
</div>

I have also included a button that triggers a function ("func()") that has the exact same logic as the postMessage handler.

Now, when I click the button in this window, I'll get the expected behavior: The correct number of "NEW FIELD BOX" divs will appear in this new window. However, when I press the original button from the main screen that posts a message over the BroadcastChannel, it WILL NOT update the UI to display the right number of "NEW FIELD BOX" divs. Using break points I can see that the array newFields does contain the right number of values, but ngFor does not re-render.

Example: I click the button on the main page that fires the onAddNewFieldClick(). It opens a new window which has one "NEW FIELD BOX" div. I click this button again which posts a message to add another. Still, only one remains on the window. I now click the button within the window that fires the function "func()." This will now render 3 "NEW FIELD BOX" divs (the original one from initialization, the one from the post message that didn't render, and the one from clicking this button).

Any ideas why change detection doesn't seem to happen from a postMessage?


Solution

  • The newFieldChannelOnMessage event handler is probably running outside of the Angular zone and does not trigger change detection. Try wrapping the code in NgZone.run():

    import { NgZone } from "@angular/core";
    
    constructor(private ngZone: NgZone) { ... }
    
    private newFieldChannelOnMessage(event: MessageEvent) {
      this.ngZone.run(() => {
        this.newFields.push(event.data as number);
      });
    }