From dc8973674fbaae54cdbb13a619b64a56350b80e0 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 14 Oct 2019 19:11:23 -0500 Subject: [PATCH] feat(concatWith): adds concatWith (#4988) `concat` operator, not the `concat` static function, is now deprecated, but in favor of `concatWith`. NOTE: First real usage of `TestScheduler` run mode in library. Enabled me to format the document with Prettier :) --- spec-dtslint/helpers.ts | 31 +- spec-dtslint/operators/concatWith-spec.ts | 62 ++++ spec-dtslint/operators/merge-spec.ts | 42 +-- spec/helpers/test-helper.ts | 16 + spec/operators/concatWith-spec.ts | 341 ++++++++++++++++++++++ src/internal/operators/concat.ts | 18 +- src/internal/operators/concatWith.ts | 48 +++ src/operators/index.ts | 1 + 8 files changed, 519 insertions(+), 40 deletions(-) create mode 100644 spec-dtslint/operators/concatWith-spec.ts create mode 100644 spec/operators/concatWith-spec.ts create mode 100644 src/internal/operators/concatWith.ts diff --git a/spec-dtslint/helpers.ts b/spec-dtslint/helpers.ts index 1f4feca793..107e0e20b4 100644 --- a/spec-dtslint/helpers.ts +++ b/spec-dtslint/helpers.ts @@ -11,13 +11,24 @@ export class H { h = 0; } export class I { i = 0; } export class J { j = 0; } -export const a = of(new A()); -export const b = of(new B()); -export const c = of(new C()); -export const d = of(new D()); -export const e = of(new E()); -export const f = of(new F()); -export const g = of(new G()); -export const h = of(new H()); -export const i = of(new I()); -export const j = of(new J()); +export const a = new A(); +export const b = new B(); +export const c = new C(); +export const d = new D(); +export const e = new E(); +export const f = new F(); +export const g = new G(); +export const h = new H(); +export const i = new I(); +export const j = new J(); + +export const a$ = of(new A()); +export const b$ = of(new B()); +export const c$ = of(new C()); +export const d$ = of(new D()); +export const e$ = of(new E()); +export const f$ = of(new F()); +export const g$ = of(new G()); +export const h$ = of(new H()); +export const i$ = of(new I()); +export const j$ = of(new J()); diff --git a/spec-dtslint/operators/concatWith-spec.ts b/spec-dtslint/operators/concatWith-spec.ts new file mode 100644 index 0000000000..9248195e77 --- /dev/null +++ b/spec-dtslint/operators/concatWith-spec.ts @@ -0,0 +1,62 @@ +import { of } from 'rxjs'; +import { concatWith } from 'rxjs/operators'; +import { a, b$, c$, d$, e$ } from 'helpers'; + +it('should support rest params', () => { + const arr = [b$, c$]; + const o = of(a).pipe(concatWith(...arr)); // $ExpectType Observable + const o2 = of(a).pipe(concatWith(d$, ...arr, e$)); // $ExpectType Observable +}); + +it('should infer correctly', () => { + const o = of(1, 2, 3).pipe(concatWith()); // $ExpectType Observable +}); + +it('should support one argument', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1))); // $ExpectType Observable +}); + +it('should support two arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2))); // $ExpectType Observable +}); + +it('should support three arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2), of(3))); // $ExpectType Observable +}); + +it('should support four arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2), of(3), of(4))); // $ExpectType Observable +}); + +it('should support five arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2), of(3), of(4), of(5))); // $ExpectType Observable +}); + +it('should support six arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2), of(3), of(4), of(5), of(6))); // $ExpectType Observable +}); + +it('should support six or more arguments', () => { + const o = of(1, 2, 3).pipe(concatWith(of(1), of(2), of(3), of(4), of(5), of(6), of(7), of(8), of(9))); // $ExpectType Observable +}); + +it('should support promises', () => { + const o = of(1, 2, 3).pipe(concatWith(Promise.resolve(4))); // $ExpectType Observable +}); + +it('should support arrays', () => { + const o = of(1, 2, 3).pipe(concatWith([4, 5])); // $ExpectType Observable +}); + +it('should support iterables', () => { + const o = of(1, 2, 3).pipe(concatWith('foo')); // $ExpectType Observable +}); + +it('should infer correctly with multiple types', () => { + const o = of(1, 2, 3).pipe(concatWith(of('foo'), Promise.resolve([1]), of(6))); // $ExpectType Observable +}); + +it('should enforce types', () => { + const o = of(1, 2, 3).pipe(concatWith(5)); // $ExpectError + const p = of(1, 2, 3).pipe(concatWith(of(5), 6)); // $ExpectError +}); diff --git a/spec-dtslint/operators/merge-spec.ts b/spec-dtslint/operators/merge-spec.ts index acea883581..71bd76ac2a 100644 --- a/spec-dtslint/operators/merge-spec.ts +++ b/spec-dtslint/operators/merge-spec.ts @@ -1,81 +1,81 @@ -import { of, asyncScheduler } from 'rxjs'; +import { asyncScheduler } from 'rxjs'; import { merge } from 'rxjs/operators'; -import { A, B, C, D, E, F, G, a, b, c, d, e, f, g } from '../helpers'; +import { a$, b$, c$, d$, e$, f$} from '../helpers'; it('should accept no parameter', () => { - const res = a.pipe(merge()); // $ExpectType Observable + const res = a$.pipe(merge()); // $ExpectType Observable }); it('should infer correctly with scheduler param', () => { - const res = a.pipe(merge(asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with concurrent param', () => { - const res = a.pipe(merge(3)); // $ExpectType Observable + const res = a$.pipe(merge(3)); // $ExpectType Observable }); it('should infer correctly with concurrent and scheduler param', () => { - const res = a.pipe(merge(3, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(3, asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with 1 Observable param', () => { - const res = a.pipe(merge(b)); // $ExpectType Observable + const res = a$.pipe(merge(b$)); // $ExpectType Observable }); it('should infer correctly with 2 Observable param', () => { - const res = a.pipe(merge(b, c)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$)); // $ExpectType Observable }); it('should infer correctly with 3 Observable param', () => { - const res = a.pipe(merge(b, c, d)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$)); // $ExpectType Observable }); it('should infer correctly with 4 Observable param', () => { - const res = a.pipe(merge(b, c, d, e)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$)); // $ExpectType Observable }); it('should infer correctly with 5 Observable param', () => { - const res = a.pipe(merge(b, c, d, e, f)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$, f$)); // $ExpectType Observable }); it('should infer correctly with 1 Observable and concurrent param', () => { - const res = a.pipe(merge(b, 1)); // $ExpectType Observable + const res = a$.pipe(merge(b$, 1)); // $ExpectType Observable }); it('should infer correctly with 2 Observable and concurrent param', () => { - const res = a.pipe(merge(b, c, 1)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, 1)); // $ExpectType Observable }); it('should infer correctly with 3 Observable and concurrent param', () => { - const res = a.pipe(merge(b, c, d, 1)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, 1)); // $ExpectType Observable }); it('should infer correctly with 4 Observable and concurrent param', () => { - const res = a.pipe(merge(b, c, d, e, 1)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$, 1)); // $ExpectType Observable }); it('should infer correctly with 5 Observable and concurrent param', () => { - const res = a.pipe(merge(b, c, d, e, f, 1)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$, f$, 1)); // $ExpectType Observable }); it('should infer correctly with 1 Observable, concurrent, and scheduler param', () => { - const res = a.pipe(merge(b, 1, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(b$, 1, asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with 2 Observable, concurrent, and scheduler param', () => { - const res = a.pipe(merge(b, c, 1, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, 1, asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with 3 Observable, concurrent, and scheduler param', () => { - const res = a.pipe(merge(b, c, d, 1, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, 1, asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with 4 Observable, concurrent, and scheduler param', () => { - const res = a.pipe(merge(b, c, d, e, 1, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$, 1, asyncScheduler)); // $ExpectType Observable }); it('should infer correctly with 5 Observable, concurrent, and scheduler param', () => { - const res = a.pipe(merge(b, c, d, e, f, 1, asyncScheduler)); // $ExpectType Observable + const res = a$.pipe(merge(b$, c$, d$, e$, f$, 1, asyncScheduler)); // $ExpectType Observable }); // TODO: Fix this when the both merge operator and merge creator function has been fix diff --git a/spec/helpers/test-helper.ts b/spec/helpers/test-helper.ts index 5043bf7d18..b2d832937d 100644 --- a/spec/helpers/test-helper.ts +++ b/spec/helpers/test-helper.ts @@ -5,6 +5,7 @@ import { root } from 'rxjs/internal/util/root'; import { observable } from 'rxjs/internal/symbol/observable'; import { iterator } from 'rxjs/internal/symbol/iterator'; import * as sinon from 'sinon'; +import { expect } from 'chai'; export function lowerCaseO(...args: Array): Observable { const o = { @@ -47,6 +48,21 @@ export const createObservableInputs = (value: T) => of( } as any ) as Observable>; +/** + * Used to signify no subscriptions took place to `expectSubscriptions` assertions. + */ +export const NO_SUBS: string[] = []; + +/** + * Does a deep equality assertion. Used to set up {@link TestScheduler}, so that + * trees of marbles can be compared. + * @param actual The value to run the expectation against. + * @param expected The value expected. + */ +export function assertDeepEquals (actual: any, expected: any) { + expect(actual).to.deep.equal(expected); +} + global.__root__ = root; let _raf: any; diff --git a/spec/operators/concatWith-spec.ts b/spec/operators/concatWith-spec.ts new file mode 100644 index 0000000000..918754ef08 --- /dev/null +++ b/spec/operators/concatWith-spec.ts @@ -0,0 +1,341 @@ +import { expect } from 'chai'; +import { of, Observable } from 'rxjs'; +import { concatWith, mergeMap } from 'rxjs/operators'; +import { TestScheduler } from 'rxjs/testing'; +import { assertDeepEquals, NO_SUBS } from 'spec/helpers/test-helper'; + +/** @test {concat} */ +describe('concat operator', () => { + let rxTest: TestScheduler; + + beforeEach(() => { + rxTest = new TestScheduler(assertDeepEquals); + }); + + it('should concatenate two cold observables', () => { + rxTest.run(({ cold, expectObservable }) => { + const e1 = cold(' --a--b-|'); + const e2 = cold(' --x---y--|'); + const expected = '--a--b---x---y--|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + }); + }); + + it('should work properly with scalar observables', done => { + const results: string[] = []; + + const s1 = new Observable(observer => { + setTimeout(() => { + observer.next(1); + observer.complete(); + }); + }).pipe(concatWith(of(2))); + + s1.subscribe( + x => { + results.push('Next: ' + x); + }, + x => { + done(new Error('should not be called')); + }, + () => { + results.push('Completed'); + expect(results).to.deep.equal(['Next: 1', 'Next: 2', 'Completed']); + done(); + } + ); + }); + + it('should complete without emit if both sources are empty', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ----|'); + const e2subs = ' --^---!'; + const expected = '------|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should not complete if first source does not completes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---'); + const e1subs = ' ^--'; + const e2 = cold(' --|'); + const e2subs = NO_SUBS; + const expected = '---'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should not complete if second source does not completes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ---'); + const e2subs = ' --^--'; + const expected = '-----'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should not complete if both sources do not complete', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---'); + const e1subs = ' ^--'; + const e2 = cold(' ---'); + const e2subs = NO_SUBS; + const expected = '---'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should raise error when first source is empty, second source raises error', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' ----#'); + const e2subs = ' --^---!'; + const expected = '------#'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should raise error when first source raises error, second source is empty', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---#'); + const e1subs = ' ^--!'; + const e2 = cold(' ----|'); + const expected = '---#'; + const e2subs = NO_SUBS; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should raise first error when both source raise error', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---#'); + const e1subs = ' ^--!'; + const e2 = cold(' ------#'); + const expected = '---#'; + const e2subs = NO_SUBS; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should concat if first source emits once, second source is empty', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' --------|'); + const e2subs = ' -----^-------!'; + const expected = '--a----------|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should concat if first source is empty, second source emits once', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --|'); + const e1subs = ' ^-!'; + const e2 = cold(' --a--|'); + const e2subs = ' --^----!'; + const expected = '----a--|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should emit element from first source, and should not complete if second ' + 'source does not completes', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' ---'); + const e2subs = ' -----^--'; + const expected = '--a-----'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should not complete if first source does not complete', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---'); + const e1subs = ' ^--'; + const e2 = cold(' --a--|'); + const e2subs = NO_SUBS; + const expected = '---'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should emit elements from each source when source emit once', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a|'); + const e1subs = ' ^---!'; + const e2 = cold(' -----b--|'); + const e2subs = ' ----^-------!'; + const expected = '---a-----b--|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should unsubscribe to inner source if outer is unsubscribed early', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-a--a| '); + const e1subs = ' ^--------! '; + const e2 = cold(' -----b-b--b-|'); + const e2subs = ' ---------^-------!'; + const unsub = ' -----------------! '; + const expected = ' ---a-a--a-----b-b '; + + expectObservable(e1.pipe(concatWith(e2)), unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should not break unsubscription chains when result is unsubscribed explicitly', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-a--a| '); + const e1subs = ' ^--------! '; + const e2 = cold(' -----b-b--b-|'); + const e2subs = ' ---------^--------! '; + const expected = '---a-a--a-----b-b- '; + const unsub = ' ------------------! '; + + const result = e1.pipe( + mergeMap(x => of(x)), + concatWith(e2), + mergeMap(x => of(x)) + ); + + expectObservable(result, unsub).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should raise error from first source and does not emit from second source', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --#'); + const e1subs = ' ^-!'; + const e2 = cold(' ----a--|'); + const e2subs = NO_SUBS; + const expected = '--#'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should emit element from first source then raise error from second source', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' --a--|'); + const e1subs = ' ^----!'; + const e2 = cold(' -------#'); + const e2subs = ' -----^------!'; + const expected = '--a---------#'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it( + 'should emit all elements from both hot observable sources if first source ' + + 'completes before second source starts emit', + () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b-|'); + const e1subs = ' ^------!'; + const e2 = hot(' --------x--y--|'); + const e2subs = ' -------^------!'; + const expected = '--a--b--x--y--|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + } + ); + + it( + 'should emit elements from second source regardless of completion time ' + 'when second source is cold observable', + () => { + rxTest.run(({ hot, cold, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c---|'); + const e1subs = ' ^-----------!'; + const e2 = cold(' -x-y-z-|'); + const e2subs = ' ------------^------!'; + const expected = '--a--b--c----x-y-z-|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + } + ); + + it('should not emit collapsing element from second source', () => { + rxTest.run(({ hot, expectObservable, expectSubscriptions }) => { + const e1 = hot(' --a--b--c--|'); + const e1subs = ' ^----------!'; + const e2 = hot(' --------x--y--z--|'); + const e2subs = ' -----------^-----!'; + const expected = '--a--b--c--y--z--|'; + + expectObservable(e1.pipe(concatWith(e2))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + expectSubscriptions(e2.subscriptions).toBe(e2subs); + }); + }); + + it('should emit self without parameters', () => { + rxTest.run(({ cold, expectObservable, expectSubscriptions }) => { + const e1 = cold(' ---a-|'); + const e1subs = ' ^----!'; + const expected = '---a-|'; + + expectObservable(e1.pipe(concatWith())).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + }); +}); diff --git a/src/internal/operators/concat.ts b/src/internal/operators/concat.ts index 46871cccee..42f86abb76 100644 --- a/src/internal/operators/concat.ts +++ b/src/internal/operators/concat.ts @@ -3,26 +3,26 @@ import { Observable } from '../Observable'; import { ObservableInput, OperatorFunction, MonoTypeOperatorFunction, SchedulerLike } from '../types'; /* tslint:disable:max-line-length */ -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(scheduler?: SchedulerLike): MonoTypeOperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(v2: ObservableInput, scheduler?: SchedulerLike): OperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(v2: ObservableInput, v3: ObservableInput, scheduler?: SchedulerLike): OperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, scheduler?: SchedulerLike): OperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, v5: ObservableInput, scheduler?: SchedulerLike): OperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(v2: ObservableInput, v3: ObservableInput, v4: ObservableInput, v5: ObservableInput, v6: ObservableInput, scheduler?: SchedulerLike): OperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(...observables: Array | SchedulerLike>): MonoTypeOperatorFunction; -/** @deprecated Deprecated in favor of static concat. */ +/** @deprecated remove in v8. Use {@link concatWith} */ export function concat(...observables: Array | SchedulerLike>): OperatorFunction; /* tslint:enable:max-line-length */ /** - * @deprecated Deprecated in favor of static {@link concat}. + * @deprecated remove in v8. Use {@link concatWith} */ export function concat(...observables: Array | SchedulerLike>): OperatorFunction { return (source: Observable) => source.lift.call(concatStatic(source, ...(observables as any[]))); diff --git a/src/internal/operators/concatWith.ts b/src/internal/operators/concatWith.ts new file mode 100644 index 0000000000..a67c827af7 --- /dev/null +++ b/src/internal/operators/concatWith.ts @@ -0,0 +1,48 @@ +import { concat as concatStatic } from '../observable/concat'; +import { Observable } from '../Observable'; +import { ObservableInput, OperatorFunction, ObservedValuesFromArray } from '../types'; + +export function concatWith(): OperatorFunction; +export function concatWith[]>(...otherSources: A): OperatorFunction | T>; + +/** + * Emits all of the values from the source observable, then, once it completes, subscribes + * to each observable source provided, one at a time, emitting all of their values, and not subscribing + * to the next one until it completes. + * + * `concat(a$, b$, c$)` is the same as `a$.pipe(concatWith(b$, c$))`. + * + * ## Example + * + * Listen for one mouse click, then listen for all mouse moves. + * + * ```ts + * import { fromEvent } from 'rxjs'; + * import { concatWith } from 'rxjs/operators'; + * + * const clicks$ = fromEvent(document, 'click'); + * const moves$ = fromEvent(document, 'mousemove'); + * + * clicks$.pipe( + * map(() => 'click'), + * take(1), + * concatWith( + * moves$.pipe( + * map(() => 'move') + * ) + * ) + * ) + * .subscribe(x => console.log(x)); + * + * // 'click' + * // 'move' + * // 'move' + * // 'move' + * // ... + * ``` + * + * @param otherSources Other observable sources to subscribe to, in sequence, after the original source is complete. + */ +export function concatWith[]>(...otherSources: A): OperatorFunction | T> { + return (source: Observable) => source.lift.call(concatStatic(source, ...otherSources)); +} diff --git a/src/operators/index.ts b/src/operators/index.ts index 53b8e697b4..80a311fe10 100644 --- a/src/operators/index.ts +++ b/src/operators/index.ts @@ -14,6 +14,7 @@ export { concat } from '../internal/operators/concat'; export { concatAll } from '../internal/operators/concatAll'; export { concatMap } from '../internal/operators/concatMap'; export { concatMapTo } from '../internal/operators/concatMapTo'; +export { concatWith } from '../internal/operators/concatWith'; export { count } from '../internal/operators/count'; export { debounce } from '../internal/operators/debounce'; export { debounceTime } from '../internal/operators/debounceTime';