DEV Community

Hugo Delatte
Hugo Delatte

Posted on • Edited on

Implement a simple bus event between NgRx ComponentStores

What is EDA ?

The bus event refer to event driven architecture ( EDA ). The main purpose of the EDA is to break coupling between services by opposition to the service driven architecture ( SOA ).

In SOA when a consumer need a service, he directly ask to a supplier to provide this service. In this classique composition, we need to know in the consumer, which provider will resolve our issue: We have in SOA a strong coupling between services.

With the EDA, the services emit events when they make particular actions. It is the task of the other services to listen these events and to do their jobs at this moment. In this architecture the emitting service never know and dont needs to know which provider should be called.

In the EDA, we use bus events in order to transmit events. All the services will subscribe to the bus event and only listen the events they need to handle.

Bus event implementation

Our bus event is really simple in fact. We have declared into the core module (provided in all the application) an rxjs Subject.

{ provide: EVENT_MESSAGE, useValue: <EventMessageBus>new Subject<DomainEvent>() }
Enter fullscreen mode Exit fullscreen mode

From this we just need two more things to use this bus. one way to publish and one way to subscribe.

You will retrieve two services that will carry these tasks:

  • The EventHandler, will just subscribe to the subject and filter the events you need,
  • The EventPublisher, just publish event you give him as input.

Let’s see a small example right now to picture the concept…

Exemple : The Toast service

We actually use a Toast service in our application. When some actions are done by our services or when we receive data from the server we want to display toasts to warn the user that some events occurs.

We have obviously created a Toast service that is capable to display toast when we give him a message, a title and, a status. But the main problem from this point is that, we will have to call this service everywhere in the application in order to use it.

To avoid it, the bus event will clearly help us. Instead of calling the toast service, we will just push events into the bus event. The toast service will subscribe to the relevant event and handle them by displaying a toast.

First of all, inject the event publisher into your service:

constructor(private readonly eventPublisher: EventPublisher) {}
Enter fullscreen mode Exit fullscreen mode

Use the publisher to publish en event into the bus when an action needs to be triggered. Here we try to update a client into the ClientEditionStore bu we want to handle cases there is an error that come back from the server, so we publish the ClientEditionErrorEvent

readonly updateClient = this.effect((origin$: Observable<Omit<Client, 'contacts' | 'sequenceEnabled'>>) => {
    return origin$.pipe(
      switchMap((client) => this.clientsHttpService.updateClient(client.id, client)),
      catchError(() => {
        this.eventPublisher.publish(new ClientEditionErrorEvent());
      }),
    );
  });
Enter fullscreen mode Exit fullscreen mode

Of course you will have to define this event before using it:

export class ClientEditionErrorEvent extends DomainEvent {}
Enter fullscreen mode Exit fullscreen mode

The DomainEvent is actually the super class of the event we can publish into the bus event . If you check in the first part of this guide you will retrieve this class into the Subject injection.

It is all you need to do in this service but you need to handle this event in the toast service now!

First of all inject the event handler:

constructor(private readonly eventHandler: EventHandler) {}
Enter fullscreen mode Exit fullscreen mode

And you can subscribe / handle to the bus event and display your toast here

initToaster() {
    this.eventHandler
      .handle(
        ClientEditionErrorEvent,
      )
      .subscribe((event) => this.showToastByEvent(event));
  }
Enter fullscreen mode Exit fullscreen mode

When use a bus event ?

In our application we can use bus event in different use cases:

  • To allow stores to discuss and exchange information,
  • To allow application to scale without adding links between services,
  • To allow an easy handling of the toasts,
  • To allow an easy handling of the server errors

Mistakes to avoid

Do not emit events that are handled in the same service ❌

readonly deleteStep = this.effect((origin$: Observable<{ sequenceId: string; stepId: string }>) => {
    return origin$.pipe(
      switchMap(({ sequenceId, stepId }) => this.sequenceService.deleteStep(sequenceId, stepId)),
      tapAndRetry(
        () => this.eventPublisher.publish(new DeleteStepSuccessEvent()),
        () => this.eventPublisher.publish(new DeleteStepErrorEvent()),
      ),
    );
  });x
Enter fullscreen mode Exit fullscreen mode
this.eventHandler
      .handle(DeleteStepSuccessEvent, SequenceUpdateEnableSuccessEvent, SequenceSettingsUpdateOrCreateSuccessEvent)
      .pipe(
        untilDestroyed(this),
        withLatestFrom(this.sequence$),
        map(([_, sequence]) => sequence),
        filterUndefined(),
        tap((sequence) => this.fetchSequence(sequence.id)),
      )
      .subscribe();
Enter fullscreen mode Exit fullscreen mode

Top comments (0)