Skip to content

Commit

Permalink
refactor: rename runOnCleanUp to runOnInstanceDestroy
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbe812 committed Jul 10, 2024
1 parent 23e69dd commit 8258f4f
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 70 deletions.
108 changes: 54 additions & 54 deletions libs/effects/src/lib/effects-factory-function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,72 @@ import { Component, inject, OnDestroy } from '@angular/core';
import { of, Subject } from 'rxjs';

describe(`${rxEffect.name} factory function`, () => {
it('should execute all effects', async () => {
const { service, component } = await setup();
it('should execute all effects', async () => {
const { service, component } = await setup();

component.trigger$$.next(10);
component.trigger$$.next(20);
component.trigger$$.next(10);
component.trigger$$.next(20);

expect(service.sourceEffect).toHaveBeenCalledWith(10);
expect(service.triggerEffect).toBeCalledTimes(2);
expect(service.triggerEffect).toHaveBeenNthCalledWith(1, 10);
expect(service.triggerEffect).toHaveBeenNthCalledWith(2, 20);
});
it('should unsubscribe from sources onDestroy', async () => {
const { service, component, fixture } = await setup();
expect(service.sourceEffect).toHaveBeenCalledWith(10);
expect(service.triggerEffect).toBeCalledTimes(2);
expect(service.triggerEffect).toHaveBeenNthCalledWith(1, 10);
expect(service.triggerEffect).toHaveBeenNthCalledWith(2, 20);
});
it('should unsubscribe from sources onDestroy', async () => {
const { service, component, fixture } = await setup();

fixture.destroy();
component.trigger$$.next(10);
fixture.destroy();
component.trigger$$.next(10);

expect(service.triggerEffect).not.toHaveBeenCalled();
});
it('should execute teardown function onDestroy', async () => {
const { component, fixture } = await setup();
expect(service.triggerEffect).not.toHaveBeenCalled();
});
it('should execute teardown function onDestroy', async () => {
const { component, fixture } = await setup();
jest.spyOn(component.clearInterval$$, 'next');
fixture.destroy();

expect(component.clearInterval$$.next).toHaveBeenCalled();
});

describe('cleanUp', () => {
it('should execute runOnCleanUp function when terminate is called', async () => {
const { component } = await setup();
jest.spyOn(component.clearInterval$$, 'next');
fixture.destroy();
component.effects.cleanUp();

expect(component.clearInterval$$.next).toHaveBeenCalled();
});
it('should unsubscribe from all sources when cleanUp is called', async () => {
const { service, component } = await setup();

describe('cleanUp', () => {
it('should execute runOnCleanUp function when terminate is called', async () => {
const { component } = await setup();
jest.spyOn(component.clearInterval$$, 'next');
component.effects.cleanUp();

expect(component.clearInterval$$.next).toHaveBeenCalled();
});
it('should unsubscribe from all sources when cleanUp is called', async () => {
const { service, component } = await setup();

component.effects.cleanUp();
component.trigger$$.next(10);
component.effects.cleanUp();
component.trigger$$.next(10);

expect(service.triggerEffect).not.toHaveBeenCalled();
});
expect(service.triggerEffect).not.toHaveBeenCalled();
});
});

describe('runOnCleanUp', () => {
it('should execute effect onDestroy', async () => {
const { service, fixture } = await setup();
fixture.destroy();
describe('runOnCleanUp', () => {
it('should execute effect onDestroy', async () => {
const { service, fixture } = await setup();
fixture.destroy();

expect(service.teardownEffect).toHaveBeenCalled();
});
expect(service.teardownEffect).toHaveBeenCalled();
});
describe('cleanUp', () => {
it('should unsubscribe trigger$$ when unsubscribeTrigger$$ emits', async () => {
const { component, service } = await setup();
});
describe('cleanUp', () => {
it('should unsubscribe trigger$$ when unsubscribeTrigger$$ emits', async () => {
const { component, service } = await setup();

component.trigger$$.next(10);
component.unsubscribeTrigger$$.next(void 0);
component.trigger$$.next(20);
component.trigger$$.next(10);
component.unsubscribeTrigger$$.next(void 0);
component.trigger$$.next(20);

expect(service.triggerEffect).toBeCalledTimes(1);
expect(service.triggerEffect).toHaveBeenCalledWith(10);
});
expect(service.triggerEffect).toBeCalledTimes(1);
expect(service.triggerEffect).toHaveBeenCalledWith(10);
});

})

});
});

async function setup() {
const serviceMock = {
Expand Down Expand Up @@ -120,15 +118,17 @@ class TestComponent implements OnDestroy {
source$ = of(10);
clearInterval$$ = new Subject();

effects = rxEffect(({ run, runOnInstanceCleanUp }) => {
effects = rxEffect(({ run, runOnInstanceDestroy }) => {
run(this.source$, (v) => this.service.sourceEffect(v));
const triggerEffect = run(this.trigger$$, (v) => this.service.triggerEffect(v));
const triggerEffect = run(this.trigger$$, (v) =>
this.service.triggerEffect(v)
);

run(this.unsubscribeTrigger$$, () => {
triggerEffect.cleanUp();
});

runOnInstanceCleanUp(() => {
runOnInstanceDestroy(() => {
this.service.teardownEffect();
this.clearInterval$$.next(void 0);
});
Expand Down
2 changes: 1 addition & 1 deletion libs/effects/src/lib/effects-standalone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe(`${rxEffect.name} standalone`, () => {
it('should execute functions registered in runOnInstanceCleanUp ', async () => {
const { effects, fixture } = await setupSingleEffectsInstance();
const spy = jest.fn();
effects.runOnInstanceCleanUp(() => spy());
effects.runOnInstanceDestroy(() => spy());

fixture.destroy();
expect(spy).toHaveBeenCalledTimes(1);
Expand Down
61 changes: 46 additions & 15 deletions libs/effects/src/lib/effects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { DestroyRef, ErrorHandler, inject, Injectable, Injector, Optional, runInInjectionContext } from '@angular/core';
import { catchError, EMPTY, Observable, pipe, ReplaySubject, Subscription, tap } from 'rxjs';
import {
DestroyRef,
ErrorHandler,
inject,
Injectable,
Injector,
Optional,
runInInjectionContext,
} from '@angular/core';
import {
catchError,
EMPTY,
Observable,
pipe,
ReplaySubject,
Subscription,
tap,
} from 'rxjs';
import { assertInjector } from './assert-injector';

export type EffectCleanUpRef = {
Expand All @@ -11,7 +27,11 @@ type RunOptions = {
};

export function isRunOptionsGuard(obj: any): obj is RunOptions {
return typeof obj === 'object' && obj?.onCleanUp !== undefined && typeof obj.onCleanUp === 'function';
return (
typeof obj === 'object' &&
obj?.onCleanUp !== undefined &&
typeof obj.onCleanUp === 'function'
);
}

/**
Expand Down Expand Up @@ -82,7 +102,11 @@ export class Effects {
* @param sideEffectFn
* @param options
*/
run<T>(o$: Observable<T>, sideEffectFn: (arg: T) => void, options?: RunOptions): EffectCleanUpRef;
run<T>(
o$: Observable<T>,
sideEffectFn: (arg: T) => void,
options?: RunOptions
): EffectCleanUpRef;
/**
* @internal
*/
Expand All @@ -98,7 +122,9 @@ export class Effects {
this.idSubMap.set(effectId, obsOrSub$);
let runOnInstanceDestroySub: undefined | EffectCleanUpRef = undefined;
if (sideEffectFn ?? isRunOptionsGuard(sideEffectFn)) {
runOnInstanceDestroySub = this.runOnInstanceCleanUp(() => (sideEffectFn as RunOptions).onCleanUp?.());
runOnInstanceDestroySub = this.runOnInstanceDestroy(() =>
(sideEffectFn as RunOptions).onCleanUp?.()
);
}

return {
Expand All @@ -115,7 +141,9 @@ export class Effects {
const subscription = obsOrSub$
.pipe(
// execute operation/ side effect
sideEffectFn && typeof sideEffectFn === 'function' ? tap(sideEffectFn) : pipe(),
sideEffectFn && typeof sideEffectFn === 'function'
? tap(sideEffectFn)
: pipe(),
catchError((err) => {
this.errorHandler?.handleError(err);
return EMPTY;
Expand All @@ -128,10 +156,14 @@ export class Effects {
// cases sideEffectFn is a CleanUpRef OR options given
let runOnInstanceDestroySub: undefined | EffectCleanUpRef = undefined;
if (options && options.onCleanUp) {
runOnInstanceDestroySub = this.runOnInstanceCleanUp(() => options.onCleanUp?.());
runOnInstanceDestroySub = this.runOnInstanceDestroy(() =>
options.onCleanUp?.()
);
}
if (sideEffectFn && isRunOptionsGuard(sideEffectFn)) {
runOnInstanceDestroySub = this.runOnInstanceCleanUp(() => (sideEffectFn as RunOptions).onCleanUp?.());
runOnInstanceDestroySub = this.runOnInstanceDestroy(() =>
(sideEffectFn as RunOptions).onCleanUp?.()
);
}

return {
Expand All @@ -153,7 +185,7 @@ export class Effects {
* Execute a sideEffect when the rxEffect instance OnDestroy hook is executed
* @param sideEffectFn
*/
runOnInstanceCleanUp(sideEffectFn: () => void) {
runOnInstanceDestroy(sideEffectFn: () => void) {
return this.run(this.destroyHook$$.pipe(tap(sideEffectFn)).subscribe());
}

Expand All @@ -174,9 +206,9 @@ export class Effects {
}
}


type EffectsSetupFn = (rxEffect: Pick<Effects, 'run' | 'runOnInstanceCleanUp' >) => void;

type EffectsSetupFn = (
rxEffect: Pick<Effects, 'run' | 'runOnInstanceDestroy'>
) => void;

/**
* @description
Expand Down Expand Up @@ -216,12 +248,11 @@ export function rxEffect(

const teardownFn = setupFn?.({
run: effects.run.bind(effects),
runOnInstanceCleanUp: effects.runOnInstanceCleanUp.bind(effects),
runOnInstanceDestroy: effects.runOnInstanceDestroy.bind(effects),
});


destroyRef.onDestroy(() => {
effects.cleanUp()
effects.cleanUp();
});

return effects;
Expand Down

0 comments on commit 8258f4f

Please # to comment.