4

I'm trying to communicate between two browser tabs/windows using BroadcastChannel API. The communication seems to work between two instances of angular service, but changes to the values of the observable are not shown. I'm trying to achieve this using mainly the async pipe.

I have a demo of my problem at StackBlitz

I have two component that are showed using the router. (controland display)

I have a service (MessagingService) where I have a BehaviorSubject and BroadcastChannel API -communications. That service is injected into both components.

  export class MessagingService {
  private _value = 1;
  valueSubject: BehaviorSubject<number> = new BehaviorSubject<number>(this._value);
  value$: Observable<number> = this.valueSubject.asObservable();

  private bc: BroadcastChannel = new BroadcastChannel('controlChannel');

  constructor() {
    this.bc.onmessage = this.handleEvent;
  }

When I open two tabs (one in /control and one in /display) I can increment the value in /control and to receive the new values in /display at console but the values wont be updated.

Binding: <p>service value: {{ messenger.value$ | async }}</p>

Injection: constructor(private messenger: MessagingService) { }

So the question is, what I'm doing wrong? I made additional subscription in displayand the fires the next call but the async binding doesn't get updated. I'm also seeing the correct MessageEvent in that same tab with correct value in data.

EDIT: Additional testing shows that the value is updated when the button for changing local value is pressed. So it seems to be an issue with change detection.

1 Answers1

2

If you are mutating data outside of the angular context (i.e., externally), then angular will not know of the changes. You need to use ChangeDetectorRef or NgZone in your component for making angular aware of external changes and thereby triggering change detection.

In your case, Probably your service somehow breaks out of Angular's zone. That's why Angular is unable to detect Changes

This Should Work

   import { Component, OnInit, OnDestroy, NgZone } from '@angular/core';
@Component({
  selector: 'app-display',
  template:
    //.....rest of the code
    constructor(private messenger: MessagingService,private zone: NgZone,) {}

   ngOnInit() {
    this.data = new Data();
    this.sub = this.messenger.value$.subscribe(
      res => {
        this.zone.run(() => this.data.value = res)
      }
    );
  }
  1. ZONES IN ANGULAR
  2. UNDERSTANDING ZONES
  3. ANGULAR CHANGE DETECTION EXPLAINED

Forked StackBlitz

Vikas
  • 11,859
  • 7
  • 45
  • 69
  • That solves the problem mostly. Only downside is that i'll have to bind to local variable instead of using the observable directly from the service. Thanks for the reference material. I'll read up and see if I can solve this somehow with async pipe. – Jyrki Keinänen Jul 10 '18 at 19:56
  • 1
    You pointed me too right direction. Instead of using the this.zone.run in the listening component, I used it in the service and now it works without manually handling the observable subscription. [updated stackBlitz](https://stackblitz.com/edit/angular-k5lefx) – Jyrki Keinänen Jul 10 '18 at 20:24
  • I'm a bit new with Zone and service worker, I've asked a similar question, I can't put my heead around how to receive event from BroadcastChannel in angluar: https://stackoverflow.com/questions/51657007/workbox-webpack-plugin-angular-how-to-listen-to-broadcastupdate-event-in-angu – Tonio Aug 02 '18 at 16:20