Skip to content

Commit 4d98b40

Browse files
cartantbenlesh
andauthoredApr 27, 2021
feat: add config object to connectable (#6267)
* feat: add config object to connectable * chore: update api_guardian * chore: typo BREAKING CHANGE: Our very new api, `connectable`, now takes a configuration object instead of just the `Subject` instance. This was necessary to make sure it covered all use cases for what we were trying to replace in the deprecated multicasting operators. Apologies for the late-in-the-game change, but we know it's not widely used yet (it's new in v7), and we want to get it right. Co-authored-by: Ben Lesh <ben@benlesh.com>
1 parent f802843 commit 4d98b40

File tree

4 files changed

+117
-9
lines changed

4 files changed

+117
-9
lines changed
 

‎api_guard/dist/types/index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export declare const config: {
7777
useDeprecatedNextContext: boolean;
7878
};
7979

80-
export declare function connectable<T>(source: ObservableInput<T>, connector?: Subject<T>): ConnectableObservableLike<T>;
80+
export declare function connectable<T>(source: ObservableInput<T>, config?: ConnectableConfig<T>): ConnectableObservableLike<T>;
8181

8282
export declare class ConnectableObservable<T> extends Observable<T> {
8383
protected _connection: Subscription | null;

‎spec/observables/connectable-spec.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/** @prettier */
2+
import { expect } from 'chai';
3+
import { connectable, of, ReplaySubject } from 'rxjs';
4+
import { TestScheduler } from 'rxjs/testing';
5+
import { observableMatcher } from '../helpers/observableMatcher';
6+
7+
describe('connectable', () => {
8+
let testScheduler: TestScheduler;
9+
10+
beforeEach(() => {
11+
testScheduler = new TestScheduler(observableMatcher);
12+
});
13+
14+
it('should mirror a simple source Observable', () => {
15+
testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => {
16+
const source = cold('--1-2---3-4--5-|');
17+
const sourceSubs = ' ^--------------!';
18+
const expected = ' --1-2---3-4--5-|';
19+
20+
const obs = connectable(source);
21+
22+
expectObservable(obs).toBe(expected);
23+
expectSubscriptions(source.subscriptions).toBe(sourceSubs);
24+
25+
obs.connect();
26+
});
27+
});
28+
29+
it('should do nothing if connect is not called, despite subscriptions', () => {
30+
testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => {
31+
const source = cold('--1-2---3-4--5-|');
32+
const sourceSubs: string[] = [];
33+
const expected = ' -';
34+
35+
const obs = connectable(source);
36+
37+
expectObservable(obs).toBe(expected);
38+
expectSubscriptions(source.subscriptions).toBe(sourceSubs);
39+
});
40+
});
41+
42+
it('should support resetOnDisconnect = true', () => {
43+
const values: number[] = [];
44+
const source = of(1, 2, 3);
45+
const obs = connectable(source, {
46+
connector: () => new ReplaySubject(1),
47+
resetOnDisconnect: true,
48+
});
49+
50+
obs.subscribe((value) => values.push(value));
51+
const connection = obs.connect();
52+
expect(values).to.deep.equal([1, 2, 3]);
53+
54+
connection.unsubscribe();
55+
56+
obs.subscribe((value) => values.push(value));
57+
obs.connect();
58+
expect(values).to.deep.equal([1, 2, 3, 1, 2, 3]);
59+
});
60+
61+
it('should support resetOnDisconnect = false', () => {
62+
const values: number[] = [];
63+
const source = of(1, 2, 3);
64+
const obs = connectable(source, {
65+
connector: () => new ReplaySubject(1),
66+
resetOnDisconnect: false,
67+
});
68+
69+
obs.subscribe((value) => values.push(value));
70+
const connection = obs.connect();
71+
expect(values).to.deep.equal([1, 2, 3]);
72+
73+
connection.unsubscribe();
74+
75+
obs.subscribe((value) => values.push(value));
76+
obs.connect();
77+
expect(values).to.deep.equal([1, 2, 3, 3]);
78+
});
79+
});

‎src/internal/observable/connectable.ts

+36-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ObservableInput } from '../types';
1+
import { ObservableInput, SubjectLike } from '../types';
22
import { Subject } from '../Subject';
33
import { Subscription } from '../Subscription';
44
import { Observable } from '../Observable';
@@ -18,29 +18,58 @@ export interface ConnectableObservableLike<T> extends Observable<T> {
1818
connect(): Subscription;
1919
}
2020

21+
export interface ConnectableConfig<T> {
22+
/**
23+
* A factory function used to create the Subject through which the source
24+
* is multicast. By default this creates a {@link Subject}.
25+
*/
26+
connector: () => SubjectLike<T>;
27+
/**
28+
* If true, the resulting observable will reset internal state upon disconnetion
29+
* and return to a "cold" state. This allows the resulting observable to be
30+
* reconnected.
31+
* If false, upon disconnection, the connecting subject will remain the
32+
* connecting subject, meaning the resulting observable will not go "cold" again,
33+
* and subsequent repeats or resubscriptions will resubscribe to that same subject.
34+
*/
35+
resetOnDisconnect?: boolean;
36+
}
37+
38+
/**
39+
* The default configuration for `connectable`.
40+
*/
41+
const DEFAULT_CONFIG: ConnectableConfig<unknown> = {
42+
connector: () => new Subject<unknown>(),
43+
resetOnDisconnect: true,
44+
};
45+
2146
/**
2247
* Creates an observable that multicasts once `connect()` is called on it.
2348
*
2449
* @param source The observable source to make connectable.
25-
* @param connector The subject to used to multicast the source observable to all subscribers.
26-
* Defaults to a new {@link Subject}.
50+
* @param config The configuration object for `connectable`.
2751
* @returns A "connectable" observable, that has a `connect()` method, that you must call to
2852
* connect the source to all consumers through the subject provided as the connector.
2953
*/
30-
export function connectable<T>(source: ObservableInput<T>, connector: Subject<T> = new Subject<T>()): ConnectableObservableLike<T> {
54+
export function connectable<T>(source: ObservableInput<T>, config: ConnectableConfig<T> = DEFAULT_CONFIG): ConnectableObservableLike<T> {
3155
// The subscription representing the connection.
3256
let connection: Subscription | null = null;
57+
const { connector, resetOnDisconnect = true } = config;
58+
let subject = connector();
3359

3460
const result: any = new Observable<T>((subscriber) => {
35-
return connector.subscribe(subscriber);
61+
return subject.subscribe(subscriber);
3662
});
3763

3864
// Define the `connect` function. This is what users must call
3965
// in order to "connect" the source to the subject that is
4066
// multicasting it.
4167
result.connect = () => {
42-
if (!connection) {
43-
connection = defer(() => source).subscribe(connector);
68+
if (!connection || connection.closed) {
69+
connection = defer(() => source).subscribe(subject);
70+
if (resetOnDisconnect) {
71+
connection.add(() => (subject = connector()));
72+
}
4473
}
4574
return connection;
4675
};

‎src/internal/operators/connect.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ const DEFAULT_CONFIG: ConnectConfig<unknown> = {
9292
* and Observable, that when subscribed to, will utilize the multicast observable.
9393
* After this function is executed -- and its return value subscribed to -- the
9494
* the operator will subscribe to the source, and the connection will be made.
95-
* @param param0 The configuration object for `connect`.
95+
* @param config The configuration object for `connect`.
9696
*/
9797
export function connect<T, O extends ObservableInput<unknown>>(
9898
selector: (shared: Observable<T>) => O,

0 commit comments

Comments
 (0)
Please sign in to comment.