🔧 You can access the source code for everything in this article in this repository.
This article takes a traditional object-oriented approach. While these abstractions may not be necessary for all projects, they provide a solid understanding of the Observer pattern. You can choose simpler alternatives like functions and plain objects if they suit your needs better.
Note: Some symbol names in this implementation differ from the traditional Observer pattern to avoid conflicts with JavaScript’s predefined symbols.
What is the Observer pattern?
The Observer pattern is a design pattern that decouples the subject of an event from the observers who want to react to it. The subject doesn’t need to know any implementation details about the observers, only that it should notify them when an event occurs.
📩 Example: Imagine a mailing list. You can subscribe to receive notifications whenever a new message is posted. You don’t need to know how the mailing list works; you just receive the updates.
Another example is a chat application 💬, where you can subscribe to a chat room to get notified whenever a new message is posted.
As you can see, the Observer pattern is highly useful and widely applicable in real-world scenarios.
Writing the implementation
In our vanilla JavaScript implementation, we’ll utilize ES2015+ features, such as classes.
If you’d like to explore the TypeScript implementation, simply click the TypeScript
tab in any of the code snippets.
Prelude: Vanilla JS Gotchas & Workarounds
✅ If you just want to see the TypeScript implementation, feel free to skip ahead this section.
Before we start, let me introduce some practices I used to work-around some missing features in JavaScript and to enchance the overall developer experience.
First step: Abstract classes
First, we need to define our contracts or interfaces 📜. We’ll create our own abstract classes.
This runtime check makes sure that we can’t instantiate the abstract class directly.
To create a concrete class, we simply extend the abstract class.
Second step: Typing & Editor autocompletion
To workaround the lack of types in JS, we will use JSDoc comments to get better autocompletion in IDEs.
Here for example we will define a simple User class, making use of JavaScript private properties and getters.
If you hover over the fullName
property, your editor should tell you that it is of type string
💪🏻
Observer Pattern contract: Using abstract classes
👏🏻 Now that we settled everything, we can start implementing the Observer pattern.
With these interfaces and abstract classes, we can now define the behaviors that any class implementing the Observer pattern must follow.
Think of it as a contract that outlines how all future implementations should behave, in the most general terms possible.
AbstractMessage
We’ll begin with the AbstractMessage
class, which acts as a contract for communication between the subject and observers. It essentially wraps the data that will be passed to the observers.
The payload
property is suggested to return a Record<string, unknown>
because it is a common type for all messages, if you feel that you might need any kind of result type, I suggest you use the unknown
type.
💡 If you are unfamiliar with the Record type, it allows you to define key-value pairs where the key is a
string
and the value can be of any type, thanks to theunknown
type.
For the TypeScript implementation, we’ll use a generic type to define the payload type.
AbstractListener
This contract represents the observers aka any class that wants to listen to the subject’s events.
🔑 The key takeaway is that listeners receive messages and must have a unique identifier.
AbstractPublisher
This class represents a Publisher, which is the entity responsible for pushing messages to its audience.
This class includes three key behaviors:
- Adding a listener to the audience.
- Removing a listener from the audience.
- Notifying all listeners with a specific message.
Observer Pattern implementation: Concrete classes
Publisher
The Publisher
class is responsible for sending messages to its audience.
This simple implementation uses a Set to store listeners, and it identifies them using their id
property.
Listener
The Listener
class is the entity that listens for updates from the subject. Here is our concrete implementation:
Example: Implementing Message & Mailing list
With our implementation complete, here’s an example that demonstrates how to instantiate and use these classes.
With now all our implementation is done!
Here is the sample code that instanciates the classes and uses them.
Annex: UML Diagram & flowchart
Conclusion
I hope this article has been helpful in explaining the Observer pattern and its implementation. Good luck with your project!
👋 Be well.