Skip to content

Commit

Permalink
feat(context): use BindingEvent for binding event listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Jan 21, 2020
1 parent 607dc0a commit ae5febc
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 43 deletions.
26 changes: 23 additions & 3 deletions docs/site/Binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Binding<unknown>>;
/**
* Operation that triggers the event
*/
operation: string;
};

/**
* Event listeners for binding events
*/
export type BindingEventListener = (
binding: Binding<unknown>,
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);
}
Expand Down
62 changes: 27 additions & 35 deletions packages/context/src/__tests__/unit/binding.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {expect, sinon, SinonSpy} from '@loopback/testlab';
import {
Binding,
BindingEvent,
BindingScope,
BindingType,
Context,
Expand Down Expand Up @@ -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');
});
});

Expand All @@ -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');
});
});

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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');
});
});

Expand All @@ -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');
});
});

Expand Down Expand Up @@ -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');
});
});

Expand Down Expand Up @@ -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');
});
});

Expand Down Expand Up @@ -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<string> {
constructor(@inject('msg') private _msg: string) {}
value(): string {
Expand Down
39 changes: 34 additions & 5 deletions packages/context/src/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,32 @@ export type BindingTag = TagMap | string;
*/
export type BindingTemplate<T = unknown> = (binding: Binding<T>) => void;

/**
* Information for a binding event
*/
export type BindingEvent = {
/**
* Event type
*/
type: string;
/**
* Source binding that emits the event
*/
binding: Readonly<Binding<unknown>>;
/**
* Operation that triggers the event
*/
operation: string;
};

/**
* Event listeners for binding events
*/
export type BindingEventListener = (
binding: Binding<unknown>,
event: string,
/**
* Binding event
*/
event: BindingEvent,
) => void;

type ValueGetter<T> = (
Expand Down Expand Up @@ -334,6 +354,15 @@ export class Binding<T = BoundValue> 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.
Expand Down Expand Up @@ -372,7 +401,7 @@ export class Binding<T = BoundValue> extends EventEmitter {
Object.assign(this.tagMap, t);
}
}
this.emit('changed', this, 'tag');
this.emitChangedEvent('tag');
return this;
}

Expand All @@ -390,7 +419,7 @@ export class Binding<T = BoundValue> 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;
}

Expand Down Expand Up @@ -421,7 +450,7 @@ export class Binding<T = BoundValue> extends EventEmitter {
}
return getValue(ctx, options);
};
this.emit('changed', this, 'value');
this.emitChangedEvent('value');
}

/**
Expand Down

0 comments on commit ae5febc

Please sign in to comment.