diff --git a/content/collections/js/proxy.yaml b/content/collections/js/proxy.yaml index f4b2e847eda..ce04ebf0032 100644 --- a/content/collections/js/proxy.yaml +++ b/content/collections/js/proxy.yaml @@ -6,6 +6,7 @@ snippetIds: - js/s/dynamic-getter-setter-proxy - js/s/immutable-object-proxy - js/s/singleton-proxy + - js/s/observable-proxy - js/s/dynamic-getter-chain-proxy - js/s/object-array-proxy - js/s/proxy-array-negative-index diff --git a/content/snippets/js/s/observable-proxy.md b/content/snippets/js/s/observable-proxy.md new file mode 100644 index 00000000000..450658a6199 --- /dev/null +++ b/content/snippets/js/s/observable-proxy.md @@ -0,0 +1,45 @@ +--- +title: Using a Proxy to implement the Observable pattern +shortTitle: Observable Proxy +type: story +language: javascript +tags: [object,proxy] +cover: city-view +excerpt: Use the Proxy object to implement the Observable pattern in JavaScript. +dateModified: 2024-05-27 +--- + +The **Observer pattern** is a design pattern where an object (known as the subject) maintains a list of **dependents** (observers) that are notified of any changes in the object's state. With a little ingenuity, we can leverage the `EventTarget` interface and the `Proxy` object to implement the Observer pattern in JavaScript. + +> [!NOTE] +> +> I'm using the `EventTarget` interface, as it's **common between the browser and Node.js** environments. If you're only working with Node.js, the `EventEmitter` offers a more robust solution, but you'll have to make a few tweaks. + +At its heart the Observer pattern is a simple pub/sub ([publish–subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)) system. We can create a `Observable` class that extends the `EventTarget` interface and use a `Proxy` object to **intercept property changes**, via the `set` trap. + +When a property changes, a `CustomEvent` is created to notify any observers, carrying the **name of the property** as its `type` and the **new value** as its `detail`. Finally, the event will be **dispatched** via `EventTarget.dispatchEvent()`, **notifying all registered listeners**. + +```js +class Observable extends EventTarget { + constructor() { + super(); + return new Proxy(this, { + set: (target, property, value) => { + target[property] = value; + this.dispatchEvent(new CustomEvent(property, { detail: value })); + return true; + }, + }); + } +} + +const subject = new Observable(); + +subject.addEventListener('name', event => { + console.log(`Name changed to ${event.detail}`); +}); + +subject.name = 'Alice'; // Name changed to Alice +``` + +While this code is pretty simple, it can be easily extend to handle more complex use cases, such as **nested objects** or **arrays**. It can be even used to create an observable store, similar to Redux. Give it a try and see how far you can take it!