diff --git a/projects/integration/src/app/api-tests/ng-core.spec.ts b/projects/integration/src/app/api-tests/ng-core.spec.ts index 832ac60d..fc9e326f 100644 --- a/projects/integration/src/app/api-tests/ng-core.spec.ts +++ b/projects/integration/src/app/api-tests/ng-core.spec.ts @@ -20,6 +20,7 @@ import { DirectiveSuperclass, FormControlSuperclass, InjectableSuperclass, + mixInInjectableSuperclass, provideValueAccessor, WrappedFormControlSuperclass, } from '@s-libs/ng-core'; @@ -45,6 +46,10 @@ describe('ng-core', () => { expect(WrappedFormControlSuperclass).toBeDefined(); }); + it('has mixInInjectableSuperclass', () => { + expect(mixInInjectableSuperclass).toBeDefined(); + }); + it('has provideValueAccessor', () => { expect(provideValueAccessor).toBeDefined(); }); diff --git a/projects/ng-core/src/lib/injectable-superclass.spec.ts b/projects/ng-core/src/lib/injectable-superclass.spec.ts index 3d8b705a..05dac936 100644 --- a/projects/ng-core/src/lib/injectable-superclass.spec.ts +++ b/projects/ng-core/src/lib/injectable-superclass.spec.ts @@ -2,7 +2,10 @@ import { Component, Directive, Injectable } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ComponentContext, expectSingleCallAndReset } from '@s-libs/ng-dev'; import { Subject } from 'rxjs'; -import { InjectableSuperclass } from './injectable-superclass'; +import { + InjectableSuperclass, + mixInInjectableSuperclass, +} from './injectable-superclass'; @Injectable() class DestroyableService extends InjectableSuperclass {} @@ -72,3 +75,25 @@ describe('InjectableSuperclass', () => { }); }); }); + +describe('mixInInjectableSuperclass()', () => { + it('add InjectableSuperclass abilities to a subclass', () => { + class InjectableDate extends mixInInjectableSuperclass(Date) {} + const spy = jasmine.createSpy(); + const subject = new Subject(); + const dateManager = new InjectableDate(); + + dateManager.subscribeTo(subject, spy); + subject.next('value'); + + expectSingleCallAndReset(spy, 'value'); + }); + + it('retains the abilities of the other superclass', () => { + class InjectableDate extends mixInInjectableSuperclass(Date) {} + + const dateManager = new InjectableDate('2020-11-27'); + + expect(dateManager.getFullYear()).toBe(2020); + }); +}); diff --git a/projects/ng-core/src/lib/injectable-superclass.ts b/projects/ng-core/src/lib/injectable-superclass.ts index 3b5ac0dc..3ea42b33 100644 --- a/projects/ng-core/src/lib/injectable-superclass.ts +++ b/projects/ng-core/src/lib/injectable-superclass.ts @@ -1,15 +1,42 @@ import { OnDestroy } from '@angular/core'; -import { SubscriptionManager } from '@s-libs/rxjs-core'; -import { Observable, Subject } from 'rxjs'; +import { Constructor } from '@s-libs/js-core'; +import { mixInSubscriptionManager } from '@s-libs/rxjs-core'; +import { Subject } from 'rxjs'; + +/** + * Mixes in {@link InjectableSuperclass} as an additional superclass. + * + * ```ts + * class MySubclass extends mixInInjectableSuperclass(MyOtherSuperclass) { + * subscribeWithAutoUnsubscribe(observable: Observable) { + * this.subscribeTo(observable); + * } + * } + * ``` + */ +// tslint:disable-next-line:typedef +export function mixInInjectableSuperclass(Base: B) { + return class extends mixInSubscriptionManager(Base) implements OnDestroy { + #destructionSubject = new Subject(); + + /** + * An observable that emits once when this object is destroyed, then completes. + */ + destruction$ = this.#destructionSubject.asObservable(); + + ngOnDestroy(): void { + this.unsubscribe(); + this.#destructionSubject.next(); + this.#destructionSubject.complete(); + } + }; +} /** * Use as the superclass for anything managed by angular's dependency injection for care-free use of `subscribeTo()`. It simply calls `unsubscribe()` during `ngOnDestroy()`. If you override `ngOnDestroy()` in your subclass, be sure to invoke the super implementation. * * ```ts - * @Injectable() - * // or @Component() (also consider DirectiveSuperclass) - * // or @Directive() (also consider DirectiveSuperclass) - * // or @Pipe() + * @Injectable() // or @Component(), @Directive() or @Pipe(), but consider DirectiveSuperclass * class MyThing extends InjectableSuperclass { * constructor(somethingObservable: Observable) { * super(); @@ -23,24 +50,6 @@ import { Observable, Subject } from 'rxjs'; * } * ``` */ -export abstract class InjectableSuperclass - extends SubscriptionManager - implements OnDestroy { - /** - * An observable that emits once when this object is destroyed, then completes. - */ - destruction$: Observable; - - private destructionSubject = new Subject(); - - constructor() { - super(); - this.destruction$ = this.destructionSubject.asObservable(); - } - - ngOnDestroy(): void { - this.unsubscribe(); - this.destructionSubject.next(); - this.destructionSubject.complete(); - } -} +export abstract class InjectableSuperclass extends mixInInjectableSuperclass( + Object, +) {} diff --git a/projects/ng-core/src/public-api.ts b/projects/ng-core/src/public-api.ts index 67f70a34..e9458700 100644 --- a/projects/ng-core/src/public-api.ts +++ b/projects/ng-core/src/public-api.ts @@ -7,5 +7,8 @@ export { FormControlSuperclass, provideValueAccessor, } from './lib/form-control-superclass'; -export { InjectableSuperclass } from './lib/injectable-superclass'; +export { + InjectableSuperclass, + mixInInjectableSuperclass, +} from './lib/injectable-superclass'; export { WrappedFormControlSuperclass } from './lib/wrapped-form-control-superclass';