Skip to content

Messaging

At times you want to allow multiple features on a page to know about changes that happens in one component. A common scenario is a list + detail scenario where you typically have a list and when you click an item you get the details of it somewhere else on the page.

With React you would typically create a context and wrap the components needing the same information in a React context. The problem with this is that you are having to create these different contexts, which creates a set of boiler plate code that has little value and you need to put it into the correct place in the rendering hierarchy. This creates a logical coupling in your application and is error prone when changing structure.

With the MVVM model this doesn’t really work either, as the contexts aren’t available to the view model. Instead, we can leverage a publish/subscribe mechanism where components through their view model can publish messages that can be subscribed to by other parts of your system that lives on the page.

This creates for a more decoupled approach and making your code and structure easier to change.

In Cratis Arc the system that deals with this is called IMessenger. It has a method called publish() for publishing messages, and one for subscribing called subscribe(). Both of these methods work on types and require a runtime type like a class to be the message type. The implementation is provided by Arc core (@cratis/arc/messaging) and wired into MVVM bindings.

Below is an example of the view models for two components, one listing items and the other holding details.

The first thing we want is a message saying user is selected (assuming a type of User):

export class UserSelected {
constructor(readonly user: User) {
}
}

Then for the list component we would typically have something like the following:

import { IMessenger } from '@cratis/arc.react.mvvm/messaging';
@injectable()
export class UsersListViewModel {
// Take the IMessenger as a dependency
constructor(private readonly _messenger: IMessenger) {
}
selectUser(user: User) {
// Publish the message saying the user was selected
this._messenger.publish(new UserSelected(user));
}
}

Whenever a selection is done, the view would call the selectUser() method, which would then publish the message.

For the details component, the view model would then need to subscribe to this:

import { IMessenger } from '@cratis/arc.react.mvvm/messaging';
@injectable()
export class UserDetailsViewModel {
// Take the IMessenger as a dependency
constructor(messenger: IMessenger) {
messenger.subscribe(UserSelected, (user) => this.user = user)
}
// State that would be used in the view
user?: User;
}

With this, the details view model will subscribe to the UserSelected message and set the state of the view model accordingly.

When using Arc React, the root <Arc /> provides a root messenger. You can add nested scopes with MessengerScope from @cratis/arc.react/messaging. View models resolved with withViewModel() receive the nearest scoped messenger automatically, and fall back to the root messenger.

By default:

  • Messages do not bubble to parent scopes.
  • Messages from parent scopes trickle down to child scopes recursively.

Override behavior through:

  • bubbleToParent
  • trickleDownToChildren