Angular Communication Between Tabs
Tags: angular, typescript, broadcastchannel, frontend
Published: 2021-10-18 15:59:21

---
title: Angular Communication Between Tabs
published: true
description: The summary data needed to update when the state of the detail information changed. Having worked with `BroadcastChannel` in the past, I set about creating an Angular service to handle this functionality.
tags: Angular, TypeScript, BroadcastChannel, Frontend
series: Angular
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1iwaci6w0l9s38ky0pd.png
---

For a client project, I had a Summary and Detail Page. The Detail Page was opened in a new tab. They needed the summary to update when the state of the detail information changed.

Having worked with `BroadcastChannel` in the past (see [HERE](https://dev.to/rfornal/chrome-communication-between-tabs-12bg)), I set about creating a service to handle this functionality.

## Setup Code

First, I needed an interface ...

```typescript
export interface BroadcastMessage {
  type: string;
  payload: any;
}
```

## Broadcast Service

Then, there's the code ...

```typescript
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { filter } from 'rxjs/operators';

import { BroadcastMessage } from '@core/interfaces/broadcast-message';

import config from '@core/constants/config.json';

@Injectable({
  providedIn: 'root'
})
export class BroadcastService {

  broadcastChannel: any;
  onMessage = new Subject<any>();

  constructor() {
    this.initialize();
  }

  initialize() {
    const name: string = config.details.detailChangeChannel;
    this.broadcastChannel = new BroadcastChannel(name);
    this.broadcastChannel.onmessage = (message) => this.onMessage.next(message.data);
  }

  publish(message: BroadcastMessage): void {
    this.broadcastChannel.postMessage(message);
  }

  messagesOfType(type: string): Observable<BroadcastMessage> {
    return this.onMessage.pipe(
      filter(message => message.type === type)
    );
  }

}
```

As you can see, I pulled the initialization code out of the constructor; this makes it easier for me to test the code. The channel name is stored in a configuration JSON file.

There is a `publish` function that simply posts a message. In the `initialize` function we are watching the `onmessage` and passing the data to the `onMessage` Subject.

This then allows the developer to filter to the messages they are looking for using the `messagesOfType` function.

Here's a look at an implementation of `messagesOfType`.

```typescript
this.broadcastService.messagesOfType(config.details.detailChangeEvent).subscribe(this.handleBroadcastMessage.bind(this));

...

handleBroadcastMessage = (): void => {
  this.getUpdatedData();
};
```

The string passed in via `messagesOfType` above is also in the `config.json` file mentioned previously.

Here's a look at an implementation of `publish`.

```typescript
this.broadcastService.publish({ type: config.details.detailChangeEvent, payload: '' });
```

## Unit Tests

This code deserves to be tested ...

```typescript
import { TestBed } from '@angular/core/testing';

import { BroadcastService } from './broadcast.service';

import { BroadcastMessage } from '@core/interfaces/broadcast-message';

import config from '@core/constants/config.json';

describe('BroadcastService', () => {
  let service: BroadcastService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(BroadcastService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
    expect(service.broadcastChannel.name).toEqual(config.details.detailChangeChannel);
  });

  it('expects "publish" to trigger a postMessage', () => {
    const message: BroadcastMessage = { type: 'TEST', payload: 'DATA' };
    spyOn(service.broadcastChannel, 'postMessage').and.stub();
    service.publish(message);
    expect(service.broadcastChannel.postMessage).toHaveBeenCalledWith(message);
  });

  it('expects "messagesOfType" to capture and return message if type matches', (done) => {
    const type: string = 'TEST';
    const message: BroadcastMessage = { type: type, payload: 'DATA' };
    let expected: BroadcastMessage = Object.assign({}, message);
    service.messagesOfType(type).subscribe(result => {
      expect(result).toEqual(expected);
      done();
    });
    service.onMessage.next(message);
  });
});
```

## Limitations

Keep the following in mind when using the `BroadcastChannel`. It will only work when ...

* All browser windows are running on the same host and port.
* All browser windows are using the same scheme (it will not work if one app is opened with https and the other with http).
* The browser windows aren’t opened in incognito mode.
* And browser windows are opened in the same browser (there is no cross-browser compatibility).

I will leave checking browser version compatibility to [caniuse](https://caniuse.com/?search=BroadcastChannel).

## Summary

And, that's it. I now have a tool I can use in Angular to pass messages between tabs.