18 juni 2020
Leestijd 6 min
Hoe de BroadcastChannel API gebruiken met Angular
<span id="hs_cos_wrapper_name" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="text" >Hoe de BroadcastChannel API gebruiken met Angular</span>
Share this via:

Heb je ooit gehoord van de BroadcastChannel API? Wij een paar weken geleden ook niet. We stuitten er toevallig op na een zoektocht naar een oplossing waarmee we konden communiceren tussen verschillende browservensters van dezelfde oorsprong. In deze blogpost bespreken we de API zelf en leren we je hoe je de BroadcastChannel API kunt gebruiken binnen een Angular-applicatie.

De BroadcastChannel API

Stel je voor dat je een webpagina hebt geopend in meerdere tabbladen, en je wilt communiceren tussen deze tabbladen om ze up-to-date te houden. Hoe zou je dat moeten doen? Na wat speurwerk op internet kwamen we de BroadcastChannel API tegen die rechtstreeks in webbrowsers is geïmplementeerd.

Het blijkt dat deze API al sinds 2015 beschikbaar is. Mozilla Firefox 38 was de eerste browser die de specificatie overnam. In de loop van de volgende jaren volgden andere browsers Mozilla's voorbeeld.

Bekijk de demo hieronder om een voorbeeld te zien van wat er allemaal mogelijk is met deze technologie.

Broadcast Channel demo

Hoewel het een erg eenvoudige demo is, laat het toch de ware kracht van de BroadcastChannel API zien. In dit voorbeeld wordt de teller in sync gehouden tussen de twee vensters. Het is misschien geen typisch voorbeeld uit de echte wereld, maar je zou de API kunnen gebruiken om:

  • een gebruiker uit te loggen van een applicatie die in meerdere browsertabbladen draait,
  • een winkelwagentje in een ander browsertabbladsynchroon te houden,
  • en gegevens in andere tabbladen te verversen.

De BroadcastChannel is in feite een gebeurtenisbus met een producent en een of meer consumenten van de gebeurtenis:

BroadcastChannel message

Hoe een BroadcastChannel opzetten

Een BroadcastChannel maken

Het aanmaken van een BroadcastChannel is heel eenvoudig. Je hoeft geen bibliotheken te importeren in je code. Je hoeft alleen maar de constructor aan te roepen met een String die de naam bevat van het kanaal dat moet worden aangemaakt.

const broadcastkanaal = nieuw Broadcastkanaal('demo-broadcast-kanaal');

Een bericht verzenden

Nu we een kanaal hebben opgezet, kunnen we het gebruiken om berichten te posten. Het posten van een bericht kan worden gedaan door het aanroepen vanpostMessage op het BroadcastChannel dat je eerder hebt gemaakt.

this.counter++; broadcastChannel.postMessage({type: 'counter', teller: this.counter }); }

De postMessage kan allerlei objecten als bericht aannemen. Je kunt in principe alles versturen wat je wilt, zolang de consumer maar weet hoe hij met de ontvangende objecten om moet gaan. Het is echter een goed gebruik om een veld in je berichten te hebben dat het type bericht beschrijft. Dit maakt het eenvoudiger om je te abonneren op berichten van een specifiek type in plaats van een BroadcastChannel per type bericht.

Een bericht ontvangen

Aan de consumentenkant moet je een BroadcastChannel maken met dezelfde naam als aan de producentenkant. Als de namen niet overeenkomen, zul je (uiteraard) geen berichten ontvangen. Vervolgens moet je de onmessage callback implementeren.

const broadcastChannel = new BroadcastChannel('demo-broadcast-channel'); this.broadcastChannel.onmessage = (message) => { console.log('Bericht ontvangen', message); }

Het BroadcastChannel dat een bericht plaatst zal het bericht zelf niet ontvangen, zelfs als het een luisteraar geregistreerd heeft. Echter, als je een aparte BroadcastChannel instantie aanmaakt voor het posten en consumeren van berichten, dan zal hetbrowservenster dat het bericht gepost heeft het bericht ontvangen. Dat is waarschijnlijk niet iets wat je wilt. Om dit te vermijden, is het de beste gewoonte om een singleton instantie per BroadcastChannel aan te maken.

Een herbruikbare BroadcastService maken

Je wilt niet overal in je code waar je berichten moet produceren/consumeren naar de BroadcastChannel API verwijzen. Laten we in plaats daarvan een herbruikbare service maken die de logica inkapselt.Op die manier hoef je, als je ooit het BroadcastChannel wilt vervangen door een andere API, slechts één service aan te passen.

import {Observable, Subject} from 'rxjs'; import {filter} from 'rxjs/operators'; interface BroadcastMessage { type: string; payload: any; } export class BroadcastService { private broadcastChannel: BroadcastChannel; private onMessage = new Subject<any>(); constructor(broadcastChannelName: string) { this.broadcastChannel = new BroadcastChannel(broadcastChannelName); 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) ); } }

In deze specifieke service hebben we goed gebruik gemaakt van RxJS Observables. Let goed op demessagesOfType functie : in dit geval hebben we de standaard RxJS filter operator gebruikt om alleen de berichten te retourneren die overeenkomen met het opgegeven type. Mooi en eenvoudig!

De service is bijna klaar voor gebruik in je Angular-applicatie. Er is nog één uitdaging die je moet aangaan.

Werken in de Angular-zone

Als je Angular al een tijdje gebruikt, ken je waarschijnlijkde Angular Zone. Code die binnen de Angular Zone draait, zal automatisch de wijzigingsdetectie activeren.

De bovenstaande service draait niet in de Angular zone, omdat het een API gebruikt die niet in Angular haakt. Als het een bericht ontvangt en de interne status van een component bijwerkt, is Angular hier niet onmiddellijk van op de hoogte. Dat betekent dat je de wijzigingen niet onmiddellijk terugziet in de browser.Pas nadat de volgende wijzigingsdetectie is geactiveerd, zullen de resultaten zichtbaar zijn in de browser.

Om dit probleem te omzeilen, kun je een aangepaste RxJSOperatorFunction maken. Het enige doel van de OperatorFunction is om ervoor te zorgen dat elke levenscyclushaak van een Observable wordt uitgevoerd in de Angular Zone.

import { Observable, OperatorFunction } from 'rxjs'; import { NgZone } uit '@angular/core'; /** * Aangepaste OperatorFunction die ervoor zorgt dat alle levenscyclushaken van een Observable * worden uitgevoerd in de NgZone. */ exportfunctie runInZone<T>(zone: NgZone): OperatorFunction<T, T> { return (source) => { return new Observable(observer => { const onNext = (value: T) => zone.run() => observer.next(value)); const onError = (e: any) => zone.run() => observer.error(e)); const onComplete = () => zone.run() => observer.complete()); return source.subscribe(onNext, onError, onComplete); }); }.

NgZone is een door Angular geleverd object dat je kunt gebruiken om code programmatisch uit te voeren binnen Angular's zone. Het enige dat overblijft is het gebruiken van de bovenstaande OperatorFunction in onze BroadcastService.

... import {runInZone} from './run-in-zone'; export class BroadcastService { ... constructor(broadcastChannelName: string, private ngZone: NgZone) { this.broadcastChannel = new BroadcastChannel(broadcastChannelName); this.broadcastChannel.onmessage = (message) => this.onMessage.next(message.data); } ... messagesOfType(type: string): Observable<BroadcastMessage> { return this.onMessage.pipe( // Het is belangrijk dat we in de NgZone draaien. Dit zorgt ervoor dat Angular-componentwijzigingen onmiddellijk zichtbaar zijn in de browser wanneer ze worden bijgewerkt na het ontvangen van berichten. runInZone(this.ngZone), filter(message => message.type === type) ); } }.

Na het bijwerken van de service zijn wijzigingen direct zichtbaar bij het ontvangen van berichten.

De service injecteren

Je kunt Angular'sInjectionToken gebruiken om een singleton instantie van de service te maken. Declareer het InjectionToken:

export const DEMO_BROADCAST_SERVICE_TOKEN = new InjectionToken<BroadcastService>('demoBroadcastService', { factory: () => { return new BroadcastService('demo-broadcast-channel'); }, });

Injecteer de service via het InjectionToken:

constructor(@Inject(DEMO_BROADCAST_SERVICE_TOKEN) private broadcastService: BroadcastService) { }

Wordt de BroadcastChannel API overal ondersteund?

Je moet het volgende in gedachten houden als je de BroadcastService gebruikt. Het werkt alleen als

  • alle browservensters opdezelfde host en poort draaien,
  • alle browservensters hetzelfde schema gebruiken ( het zal niet werken als één app is geopend met https en de andere met http),
  • de browservenstersniet in incognitomodus zijn geopend,
  • en de browservensters zijn geopendin dezelfde browser ( er is geen cross-browser compatibiliteit).

Alle moderne browsers ondersteunen de BroadcastChannel API, behalve Safari en Internet Explorer 11 (en lager). Kijk voor een volledige lijst met compatibele browsers opCaniuse.

Als je een vergelijkbare oplossing moet implementeren in browsers die niet worden ondersteund, kun je in plaats daarvan de LocalStorage van de browser gebruiken.

Afhaalmaaltijden

In deze blogpost hebben we kort beschreven hoe je gebruik kunt maken van de BroadcastChannel API van de browser in een Angular-applicatie. We hebben ook gekeken naar een oplossing voor het koppelen van de API aan Angular's Zone. Je kunt de volledige code van de demo vinden op Stackblitz. Bovendien kun je de BroadcastChannel API documentatie raadplegen op MDN Web Docs.


Davy Steegen
Davy Steegen
Solution Engineer, ACA Group
Contact us

Want to dive deeper into this topic?

Get in touch with our experts today. They are happy to help!

ACA mug mok koffie tas
Contact us

Want to dive deeper into this topic?

Get in touch with our experts today. They are happy to help!

ACA mug mok koffie tas
Contact us

Want to dive deeper into this topic?

Get in touch with our experts today. They are happy to help!

ACA mug mok koffie tas
Contact us

Want to dive deeper into this topic?

Get in touch with our experts today. They are happy to help!

ACA mug mok koffie tas