Skip to content

Commit

Permalink
feat(ng-core): add mixInInjectableSuperclass()
Browse files Browse the repository at this point in the history
  • Loading branch information
ersimont committed Nov 27, 2020
1 parent c173fa5 commit e3cacf8
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 29 deletions.
5 changes: 5 additions & 0 deletions projects/integration/src/app/api-tests/ng-core.spec.ts
Expand Up @@ -20,6 +20,7 @@ import {
DirectiveSuperclass,
FormControlSuperclass,
InjectableSuperclass,
mixInInjectableSuperclass,
provideValueAccessor,
WrappedFormControlSuperclass,
} from '@s-libs/ng-core';
Expand All @@ -45,6 +46,10 @@ describe('ng-core', () => {
expect(WrappedFormControlSuperclass).toBeDefined();
});

it('has mixInInjectableSuperclass', () => {
expect(mixInInjectableSuperclass).toBeDefined();
});

it('has provideValueAccessor', () => {
expect(provideValueAccessor).toBeDefined();
});
Expand Down
27 changes: 26 additions & 1 deletion projects/ng-core/src/lib/injectable-superclass.spec.ts
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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);
});
});
63 changes: 36 additions & 27 deletions 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<any>) {
* this.subscribeTo(observable);
* }
* }
* ```
*/
// tslint:disable-next-line:typedef
export function mixInInjectableSuperclass<B extends Constructor>(Base: B) {
return class extends mixInSubscriptionManager(Base) implements OnDestroy {
#destructionSubject = new Subject<undefined>();

/**
* 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();
Expand All @@ -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<undefined>;

private destructionSubject = new Subject<undefined>();

constructor() {
super();
this.destruction$ = this.destructionSubject.asObservable();
}

ngOnDestroy(): void {
this.unsubscribe();
this.destructionSubject.next();
this.destructionSubject.complete();
}
}
export abstract class InjectableSuperclass extends mixInInjectableSuperclass(
Object,
) {}
5 changes: 4 additions & 1 deletion projects/ng-core/src/public-api.ts
Expand Up @@ -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';

0 comments on commit e3cacf8

Please sign in to comment.