From ae5febc35679f4d77b9970ecc26a71938a1c972e Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 21 Jan 2020 08:44:48 -0800 Subject: [PATCH] feat(context): use BindingEvent for binding event listeners --- docs/site/Binding.md | 26 +++++++- .../src/__tests__/unit/binding.unit.ts | 62 ++++++++----------- packages/context/src/binding.ts | 39 ++++++++++-- 3 files changed, 84 insertions(+), 43 deletions(-) diff --git a/docs/site/Binding.md b/docs/site/Binding.md index ef337ee089dd..62e31cd471bb 100644 --- a/docs/site/Binding.md +++ b/docs/site/Binding.md @@ -533,19 +533,39 @@ A binding can emit `changed` events upon changes triggered by methods such as The binding listener function signature is described as: ```ts +/** + * Information for a binding event + */ +export type BindingEvent = { + /** + * Event type + */ + type: string; + /** + * Source binding that emits the event + */ + binding: Readonly>; + /** + * Operation that triggers the event + */ + operation: string; +}; + /** * Event listeners for binding events */ export type BindingEventListener = ( - binding: Binding, - event: string, + /** + * Binding event + */ + event: BindingEvent, ) => void; ``` Now we can register a binding listener to be triggered when tags are changed: ```ts -const bindingListener: BindingEventListener = (binding, event) => { +const bindingListener: BindingEventListener = ({binding, operation}) => { if (event === 'tag') { console.log('Binding tags for %s %j', binding.key, binding.tagMap); } diff --git a/packages/context/src/__tests__/unit/binding.unit.ts b/packages/context/src/__tests__/unit/binding.unit.ts index dba55e710d65..ce6ae02e4a85 100644 --- a/packages/context/src/__tests__/unit/binding.unit.ts +++ b/packages/context/src/__tests__/unit/binding.unit.ts @@ -6,6 +6,7 @@ import {expect, sinon, SinonSpy} from '@loopback/testlab'; import { Binding, + BindingEvent, BindingScope, BindingType, Context, @@ -80,12 +81,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.tag('t1'); - expect(events).to.eql([{binding, op: 'tag'}]); + assertEvents(events, 'tag'); }); }); @@ -110,12 +108,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.inScope(BindingScope.TRANSIENT); - expect(events).to.eql([{binding, op: 'scope'}]); + assertEvents(events, 'scope'); }); }); @@ -144,12 +139,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.to('value'); - expect(events).to.eql([{binding, op: 'value'}]); + assertEvents(events, 'value'); }); it('rejects promise values', () => { @@ -179,12 +171,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.toDynamicValue(() => Promise.resolve('hello')); - expect(events).to.eql([{binding, op: 'value'}]); + assertEvents(events, 'value'); }); }); @@ -198,12 +187,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.toClass(MyService); - expect(events).to.eql([{binding, op: 'value'}]); + assertEvents(events, 'value'); }); }); @@ -242,12 +228,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.toProvider(MyProvider); - expect(events).to.eql([{binding, op: 'value'}]); + assertEvents(events, 'value'); }); }); @@ -316,12 +299,9 @@ describe('Binding', () => { }); it('triggers changed event', () => { - const events: unknown[] = []; - binding.on('changed', (b, op) => { - events.push({binding: b, op}); - }); + const events = listenOnBinding(); binding.toAlias('parent.options#child'); - expect(events).to.eql([{binding, op: 'value'}]); + assertEvents(events, 'value'); }); }); @@ -455,6 +435,18 @@ describe('Binding', () => { binding = new Binding(key); } + function listenOnBinding() { + const events: BindingEvent[] = []; + binding.on('changed', (event: BindingEvent) => { + events.push(event); + }); + return events; + } + + function assertEvents(events: BindingEvent[], operation: string) { + expect(events).to.eql([{binding, operation, type: 'changed'}]); + } + class MyProvider implements Provider { constructor(@inject('msg') private _msg: string) {} value(): string { diff --git a/packages/context/src/binding.ts b/packages/context/src/binding.ts index cfe5f37d8ae6..4671849feddd 100644 --- a/packages/context/src/binding.ts +++ b/packages/context/src/binding.ts @@ -140,12 +140,32 @@ export type BindingTag = TagMap | string; */ export type BindingTemplate = (binding: Binding) => void; +/** + * Information for a binding event + */ +export type BindingEvent = { + /** + * Event type + */ + type: string; + /** + * Source binding that emits the event + */ + binding: Readonly>; + /** + * Operation that triggers the event + */ + operation: string; +}; + /** * Event listeners for binding events */ export type BindingEventListener = ( - binding: Binding, - event: string, + /** + * Binding event + */ + event: BindingEvent, ) => void; type ValueGetter = ( @@ -334,6 +354,15 @@ export class Binding extends EventEmitter { return this; } + /** + * Emit a `changed` event + * @param operation - Operation that makes changes + */ + private emitChangedEvent(operation: string) { + const event: BindingEvent = {binding: this, operation, type: 'changed'}; + this.emit('changed', event); + } + /** * Tag the binding with names or name/value objects. A tag has a name and * an optional value. If not supplied, the tag name is used as the value. @@ -372,7 +401,7 @@ export class Binding extends EventEmitter { Object.assign(this.tagMap, t); } } - this.emit('changed', this, 'tag'); + this.emitChangedEvent('tag'); return this; } @@ -390,7 +419,7 @@ export class Binding extends EventEmitter { inScope(scope: BindingScope): this { if (this._scope !== scope) this._clearCache(); this._scope = scope; - this.emit('changed', this, 'scope'); + this.emitChangedEvent('scope'); return this; } @@ -421,7 +450,7 @@ export class Binding extends EventEmitter { } return getValue(ctx, options); }; - this.emit('changed', this, 'value'); + this.emitChangedEvent('value'); } /**