Communicate between loose components with a message bus

Problem. Two components must work together. You do not want either one to know the other.

Solution

Think of a classroom. A student wants to add a name to the class list, and the teacher keeps the list. The student does not know the other students. The teacher does not know who reads the board.

The message bus works the same way. A component publishes a message on a channel. Every component that subscribed to that channel receives the message. The sender does not know the receivers, and the receivers do not know the sender. This design is called Event-Driven Architecture (EDA).

The message bus brings three advantages:

A message carries one of two meanings. Keep the two apart.

An intent asks for an action. It names what the user wants, for example cart-item:remove. The intent goes up to the owner that holds the data. The owner handles it and stops the walk, so the other instances never see it.

// child: ask the owner to remove the line
this.emit("cart-item:remove", { source: this });
// owner: handle the intent, then stop the walk
events: {
    "cart-item:remove": "onCartItemRemove",
},

onCartItemRemove(event) {
    event.stopPropagation();
    // remove the line, then publish the change
}

A change reports a result. It names what happened, for example cart-item:removed. The owner publishes it after it updates its state. The change reaches every subscriber, so a component that does not know the owner can react.

// owner: report the result to the app
this.emit("cart-item:removed", { id });

Do not stop a change. Another component may need it.

Name a message handler on<EventName>, with the channel in PascalCase: onCartItemUpdated handles cart-item:updated. A channel groups a domain family, not a component class.

The components guide explains the binding syntax.

Why it works

Pitfalls

Further reading

The message bus is one part of a larger design called Event-Driven Architecture. These sources explain the ideas behind it.