diff --git a/modules/effects/spec/lazy_with_latest_from.spec.ts b/modules/effects/spec/lazy_with_latest_from.spec.ts new file mode 100644 index 0000000000..c78ffeadda --- /dev/null +++ b/modules/effects/spec/lazy_with_latest_from.spec.ts @@ -0,0 +1,24 @@ +import { of } from 'rxjs'; +import { skipWhile, tap } from 'rxjs/operators'; +import { hot } from 'jasmine-marbles'; +import { lazyWithLatestFrom } from '../src/lazy_with_latest_from'; + +describe('lazyWithLatestFrom', () => { + it('should emit the combined values of the source and provided observables, and evaluate the array of observables only when the source emits a value', () => { + let evaluated = false; + const toBeLazilyEvaluated = () => { + evaluated = true; + return of(4); + }; + + const numbers$ = hot('abc', { a: 1, b: 2, c: 3 }).pipe( + skipWhile((number) => number < 3), + tap(() => expect(evaluated).toBe(false)), + lazyWithLatestFrom(() => [toBeLazilyEvaluated()]) + ); + + expect(evaluated).toBe(false); + expect(numbers$).toBeObservable(hot('--d', { d: [3, 4] })); + expect(evaluated).toBe(true); + }); +}); diff --git a/modules/effects/src/lazy_with_latest_from.ts b/modules/effects/src/lazy_with_latest_from.ts new file mode 100644 index 0000000000..c8cb48ed72 --- /dev/null +++ b/modules/effects/src/lazy_with_latest_from.ts @@ -0,0 +1,34 @@ +import { Observable, of, OperatorFunction } from 'rxjs'; +import { concatMap, withLatestFrom } from 'rxjs/operators'; + +/** + * @description + * RxJS operator that lazily evaluates a list of Observables. + * + * @param observables A function which returns an `Observable[]`. + * @returns Returns an `OperatorFunction` with ??? (idk how to describe this) + * + * @usageNotes + * + * ** Use to mitigate performance impact of `store.select` ** + * ```ts + * effectName$ = createEffect( + * () => this.actions$.pipe( + * ofType(FeatureActions.actionOne), + * // The call to this.store.select will not be performed + * // until actionOne is received + * lazyWithLatestFrom(() => [this.store.select(getDataState)]), + * filter(([action, data]) => data.enabled), + * map(() => FeatureActions.actionTwo()) + * ) + * ); + * ``` + */ +export const lazyWithLatestFrom = [], V>( + observables: () => [...T] +) => + concatMap((value) => + of(value).pipe(withLatestFrom(...observables())) + ) as OperatorFunction }]>; + +type TypeOfObservable = T extends Observable ? U : never;