From 7a6b15d1dc85c7ed46fd13aecb0009143fd74ce1 Mon Sep 17 00:00:00 2001 From: Anthony Johnston Date: Mon, 5 Aug 2019 11:36:38 +0100 Subject: [PATCH] feat(watch): watch setters on an object --- src/IWatchHandler.ts | 3 +++ src/watch.spec.ts | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/watch.ts | 38 ++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 src/IWatchHandler.ts create mode 100644 src/watch.spec.ts create mode 100644 src/watch.ts diff --git a/src/IWatchHandler.ts b/src/IWatchHandler.ts new file mode 100644 index 0000000..1c2c14f --- /dev/null +++ b/src/IWatchHandler.ts @@ -0,0 +1,3 @@ +export interface IWatchHandler { + (value?: any): void; +} diff --git a/src/watch.spec.ts b/src/watch.spec.ts new file mode 100644 index 0000000..f2ccb11 --- /dev/null +++ b/src/watch.spec.ts @@ -0,0 +1,64 @@ +import { watch } from './watch'; + +describe('watch', () => { + const prop = 'PROPERTY'; + const value = 'VALUE'; + let o; + + beforeEach(() => { + o = {}; + }); + + it('watches for change', () => { + let counter = 0; + watch(o, prop, () => { + counter++; + }); + + o[prop] = value; + o[prop] = value; + + expect(counter).toBe(2); + }); + + it('watches for change once', () => { + let counter = 0; + watch( + o, + prop, + () => { + counter++; + }, + true + ); + + o[prop] = value; + o[prop] = value; + + expect(counter).toBe(1); + }); + + it('unwatches and keeps set value', () => { + const unwatch = watch(o, prop, () => {}); + + o[prop] = value; + unwatch(); + + expect(o[prop]).toBe(value); + }); + + it('can nest watches', () => { + let counter = 0; + watch(o, prop, () => { + counter++; + }); + watch(o, prop, () => { + counter++; + }); + + o[prop] = value; + o[prop] = value; + + expect(counter).toBe(4); + }); +}); diff --git a/src/watch.ts b/src/watch.ts new file mode 100644 index 0000000..32fc123 --- /dev/null +++ b/src/watch.ts @@ -0,0 +1,38 @@ +import { IWatchHandler } from './IWatchHandler'; + +export function watch( + o: any, + property: string, + handler: IWatchHandler, + once = false +): () => void { + + const descriptor = Object.getOwnPropertyDescriptor(o, property) || {}; + + let getValue = descriptor.get || (() => descriptor.value); + let setValue = descriptor.set || (value => (descriptor.value = value)); + + const unwatch = () => { + delete o[property]; + o[property] = getValue(); + }; + + Object.defineProperty(o, property, { + get: getValue, + set(value) { + if (once && getValue() === undefined) { + setValue(value); + unwatch(); + handler(value); + + return; + } + + setValue(value); + handler(value); + }, + configurable: true + }); + + return unwatch; +}