From c173fa5f56956dc3fb762235fc983722e819a41e Mon Sep 17 00:00:00 2001 From: ersimont <8042088+ersimont@users.noreply.github.com> Date: Fri, 27 Nov 2020 08:58:11 -0500 Subject: [PATCH] feat(rxjs-core): add `mixInSubscriptionManager()` --- .../src/app/api-tests/rxjs-core.spec.ts | 5 ++ .../ng-core/src/typing-tests/tsconfig.json | 1 + projects/rxjs-core/src/lib/index.ts | 5 +- .../src/lib/subscription-manager.spec.ts | 28 +++++++- .../rxjs-core/src/lib/subscription-manager.ts | 67 +++++++++++-------- 5 files changed, 77 insertions(+), 29 deletions(-) diff --git a/projects/integration/src/app/api-tests/rxjs-core.spec.ts b/projects/integration/src/app/api-tests/rxjs-core.spec.ts index 1f066ad9..a27a630d 100644 --- a/projects/integration/src/app/api-tests/rxjs-core.spec.ts +++ b/projects/integration/src/app/api-tests/rxjs-core.spec.ts @@ -11,6 +11,7 @@ import { mapAndCacheArrayElements, mapAndCacheObjectElements, mapToLatestFrom, + mixInSubscriptionManager, skipAfter, SubscriptionManager, withHistory, @@ -60,6 +61,10 @@ describe('rxjs-core', () => { expect(mapToLatestFrom).toBeDefined(); }); + it('has mixInSubscriptionManager', () => { + expect(mixInSubscriptionManager).toBeDefined(); + }); + it('has skipAfter', () => { expect(skipAfter).toBeDefined(); }); diff --git a/projects/ng-core/src/typing-tests/tsconfig.json b/projects/ng-core/src/typing-tests/tsconfig.json index b825ccb3..bea746c1 100644 --- a/projects/ng-core/src/typing-tests/tsconfig.json +++ b/projects/ng-core/src/typing-tests/tsconfig.json @@ -12,6 +12,7 @@ "baseUrl": "../lib", "experimentalDecorators": true, "paths": { + "@s-libs/js-core": ["../../../../dist/js-core"], "@s-libs/micro-dash": ["../../../../dist/micro-dash"], "@s-libs/rxjs-core": ["../../../../dist/rxjs-core"] } diff --git a/projects/rxjs-core/src/lib/index.ts b/projects/rxjs-core/src/lib/index.ts index e93e1b70..3e416ad9 100644 --- a/projects/rxjs-core/src/lib/index.ts +++ b/projects/rxjs-core/src/lib/index.ts @@ -1,4 +1,7 @@ export * from './operators'; export { createOperatorFunction } from './create-operator-function'; export { logToReduxDevtoolsExtension } from './devtools/log-to-redux-devtools-extension'; -export { SubscriptionManager } from './subscription-manager'; +export { + mixInSubscriptionManager, + SubscriptionManager, +} from './subscription-manager'; diff --git a/projects/rxjs-core/src/lib/subscription-manager.spec.ts b/projects/rxjs-core/src/lib/subscription-manager.spec.ts index 64c797c1..6471bdfd 100644 --- a/projects/rxjs-core/src/lib/subscription-manager.spec.ts +++ b/projects/rxjs-core/src/lib/subscription-manager.spec.ts @@ -1,5 +1,9 @@ +import { expectSingleCallAndReset } from '@s-libs/ng-dev'; import { of, Subject, throwError } from 'rxjs'; -import { SubscriptionManager } from './subscription-manager'; +import { + mixInSubscriptionManager, + SubscriptionManager, +} from './subscription-manager'; describe('SubscriptionManager', () => { let next: jasmine.Spy; @@ -132,3 +136,25 @@ describe('SubscriptionManager', () => { }); }); }); + +describe('mixInSubscriptionManager()', () => { + it('add SubscriptionManager abilities to a subclass', () => { + class DateManager extends mixInSubscriptionManager(Date) {} + const spy = jasmine.createSpy(); + const subject = new Subject(); + const dateManager = new DateManager(); + + dateManager.subscribeTo(subject, spy); + subject.next('value'); + + expectSingleCallAndReset(spy, 'value'); + }); + + it('retains the abilities of the other superclass', () => { + class DateManager extends mixInSubscriptionManager(Date) {} + + const dateManager = new DateManager('2020-11-27'); + + expect(dateManager.getFullYear()).toBe(2020); + }); +}); diff --git a/projects/rxjs-core/src/lib/subscription-manager.ts b/projects/rxjs-core/src/lib/subscription-manager.ts index a33b34e9..c762a70d 100644 --- a/projects/rxjs-core/src/lib/subscription-manager.ts +++ b/projects/rxjs-core/src/lib/subscription-manager.ts @@ -1,5 +1,44 @@ +import { Constructor } from '@s-libs/js-core'; import { Observable, Subscription, Unsubscribable } from 'rxjs'; +/** + * Mixes in {@link SubscriptionManager} as an additional superclass. + * + * ```ts + * class MySubclass extends mixInSubscriptionManager(MyOtherSuperclass) { + * subscribeAndManage(observable: Observable) { + * this.subscribeTo(observable); + * } + * } + * ``` + */ +// tslint:disable-next-line:typedef +export function mixInSubscriptionManager(Base: B) { + return class extends Base implements Unsubscribable { + #subscriptions = new Subscription(); + + subscribeTo( + observable: Observable, + next?: (value: T) => void, + error?: (error: any) => void, + complete?: () => void, + ): void { + this.#subscriptions.add( + observable.subscribe( + next?.bind(this), + error?.bind(this), + complete?.bind(this), + ), + ); + } + + unsubscribe(): void { + this.#subscriptions.unsubscribe(); + this.#subscriptions = new Subscription(); + } + }; +} + /** * Tracks all subscriptions to easily unsubscribe from them all during cleanup. Also binds callbacks to `this` for convenient use as a superclass, e.g.: * @@ -19,30 +58,4 @@ import { Observable, Subscription, Unsubscribable } from 'rxjs'; * } * ``` */ -export class SubscriptionManager implements Unsubscribable { - private subscriptions = new Subscription(); - - subscribeTo( - observable: Observable, - next?: (value: T) => void, - error?: (error: any) => void, - complete?: () => void, - ): void { - this.subscriptions.add( - observable.subscribe( - this.bind(next), - this.bind(error), - this.bind(complete), - ), - ); - } - - unsubscribe(): void { - this.subscriptions.unsubscribe(); - this.subscriptions = new Subscription(); - } - - private bind void>(fn?: T): T | undefined { - return fn?.bind(this); - } -} +export class SubscriptionManager extends mixInSubscriptionManager(Object) {}