From c19a986858f9931e4f6e8cce68da9da6bf2fe697 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Wed, 22 Jul 2020 12:19:18 +0300 Subject: [PATCH] feat: add a subscription service --- .../packages/core/src/lib/services/index.ts | 1 + .../src/lib/services/subscription.service.ts | 50 ++++++++++ .../lib/tests/subscription.service.spec.ts | 95 +++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/services/subscription.service.ts create mode 100644 npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index 064dcbd35e..e5e5286710 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -11,4 +11,5 @@ export * from './profile.service'; export * from './rest.service'; export * from './routes.service'; export * from './session-state.service'; +export * from './subscription.service'; export * from './track-by.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts b/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts new file mode 100644 index 0000000000..81739d10ae --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/subscription.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import type { OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import type { Observable, PartialObserver } from 'rxjs'; + +@Injectable() +export class SubscriptionService implements OnDestroy { + private subscription = new Subscription(); + + get isClosed() { + return this.subscription.closed; + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + reset() { + this.subscription.unsubscribe(); + this.subscription = new Subscription(); + } + + subscribe( + source$: Observable, + next?: (value: T) => void, + error?: (error: any) => void, + ): Subscription; + subscribe(source$: Observable, observer?: PartialObserver): Subscription; + subscribe( + source$: Observable, + nextOrObserver?: PartialObserver | Next, + error?: (error: any) => void, + ): Subscription { + const subscription = source$.subscribe(nextOrObserver as Next, error); + this.subscription.add(subscription); + return subscription; + } + + unsubscribe(subscription: Subscription | undefined | null) { + if (!subscription) return; + this.subscription.remove(subscription); + subscription.unsubscribe(); + } + + unsubscribeAll() { + this.subscription.unsubscribe(); + } +} + +type Next = (value: T) => void; diff --git a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts new file mode 100644 index 0000000000..95df285fb9 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts @@ -0,0 +1,95 @@ +import { of, Subscription, timer } from 'rxjs'; +import { SubscriptionService } from '../services/subscription.service'; + +describe('SubscriptionService', () => { + let service: SubscriptionService; + + beforeEach(() => { + service = new SubscriptionService(); + }); + + afterEach(() => { + service['subscription'].unsubscribe(); + }); + + describe('#subscribe', () => { + it('should subscribe to given observable with next and error functions and return the Subscription instance', () => { + const next = jest.fn(); + const error = jest.fn(); + const subscription = service.subscribe(of(null), next, error); + expect(subscription).toBeInstanceOf(Subscription); + expect(next).toHaveBeenCalledWith(null); + expect(next).toHaveBeenCalledTimes(1); + expect(error).not.toHaveBeenCalled(); + }); + + it('should subscribe to given observable with observer and return the Subscription instance', () => { + const observer = { next: jest.fn(), complete: jest.fn() }; + const subscription = service.subscribe(of(null), observer); + expect(subscription).toBeInstanceOf(Subscription); + expect(observer.next).toHaveBeenCalledWith(null); + expect(observer.next).toHaveBeenCalledTimes(1); + expect(observer.complete).toHaveBeenCalledTimes(1); + }); + }); + + describe('#isClosed', () => { + it('should return true if subscriptions are alive and false if not', () => { + service.subscribe(timer(1000), () => {}); + expect(service.isClosed).toBe(false); + + service['subscription'].unsubscribe(); + expect(service.isClosed).toBe(true); + }); + }); + + describe('#unsubscribeAll', () => { + it('should close all subscriptions and the parent subscription', () => { + const sub1 = service.subscribe(timer(1000), () => {}); + const sub2 = service.subscribe(timer(1000), () => {}); + + expect(sub1.closed).toBe(false); + expect(sub2.closed).toBe(false); + expect(service.isClosed).toBe(false); + + service.unsubscribeAll(); + + expect(sub1.closed).toBe(true); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(true); + }); + }); + + describe('#reset', () => { + it('should close all subscriptions but not the parent subscription', () => { + const sub1 = service.subscribe(timer(1000), () => {}); + const sub2 = service.subscribe(timer(1000), () => {}); + + expect(sub1.closed).toBe(false); + expect(sub2.closed).toBe(false); + expect(service.isClosed).toBe(false); + + service.reset(); + + expect(sub1.closed).toBe(true); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(false); + }); + }); + + describe('#unsubscribe', () => { + it('should unsubscribe from given subscription only', () => { + const sub1 = service.subscribe(timer(1000), () => {}); + const sub2 = service.subscribe(timer(1000), () => {}); + expect(service.isClosed).toBe(false); + + service.unsubscribe(sub1); + expect(sub1.closed).toBe(true); + expect(service.isClosed).toBe(false); + + service.unsubscribe(sub2); + expect(sub2.closed).toBe(true); + expect(service.isClosed).toBe(false); + }); + }); +});