Skip to content

Commit 3e66e66

Browse files
committed
fix(Subject): stricter types on next
BREAKING CHANGE: TS 2.8 and lower will have issues with calling `next()` on `Subject<void>`, `undefined` will need to be passed, or should upgrade to the latest version fo TypeScript.
2 parents 396e804 + 65e1bd7 commit 3e66e66

File tree

9 files changed

+74
-13
lines changed

9 files changed

+74
-13
lines changed

docs_app/content/guide/subject.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,40 @@ subject.complete();
341341
```
342342

343343
The AsyncSubject is similar to the [`last()`](/api/operators/last) operator, in that it waits for the `complete` notification in order to deliver a single value.
344+
345+
346+
## Void subject
347+
348+
Sometimes the emitted value doesn't matter as much as the fact that a value was emitted.
349+
350+
For instance, the code below signals that one second has passed.
351+
352+
```ts
353+
const subject = new Subject<string>();
354+
setTimeout(() => subject.next('dummy'), 1000);
355+
```
356+
357+
Passing a dummy value this way is clumsy and can confuse users.
358+
359+
By declaring a _void subject_, you signal that the value is irrelevant. Only the event itself matters.
360+
361+
```ts
362+
const subject = new Subject<void>();
363+
setTimeout(() => subject.next(), 1000);
364+
```
365+
366+
A complete example with context is shown below:
367+
368+
```ts
369+
import { Subject } from 'rxjs';
370+
371+
const subject = new Subject(); // Shorthand for Subject<void>
372+
373+
subject.subscribe({
374+
next: () => console.log('One second has passed')
375+
});
376+
377+
setTimeout(() => subject.next(), 1000);
378+
```
379+
380+
<span class="informal">Before version 7, the default type of Subject values was `any`. `Subject<any>` disables type checking of the emitted values, whereas `Subject<void>` prevents accidental access to the emitted value. If you want the old behavior, then replace `Subject` with `Subject<any>`.</span>

spec/Subject-spec.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ import { delay } from 'rxjs/operators';
66

77
/** @test {Subject} */
88
describe('Subject', () => {
9+
10+
it('should allow next with empty, undefined or any when created with no type', (done: MochaDone) => {
11+
const subject = new Subject();
12+
subject.subscribe(x => {
13+
expect(x).to.be.a('undefined');
14+
}, null, done);
15+
16+
const data: any = undefined;
17+
subject.next();
18+
subject.next(undefined);
19+
subject.next(data);
20+
subject.complete();
21+
});
22+
23+
it('should allow empty next when created with void type', (done: MochaDone) => {
24+
const subject = new Subject<void>();
25+
subject.subscribe(x => {
26+
expect(x).to.be.a('undefined');
27+
}, null, done);
28+
29+
subject.next();
30+
subject.complete();
31+
});
32+
933
it('should pump values right on through itself', (done: MochaDone) => {
1034
const subject = new Subject<string>();
1135
const expected = ['foo', 'bar'];
@@ -271,7 +295,7 @@ describe('Subject', () => {
271295
});
272296

273297
it('should not allow values to be nexted after it is unsubscribed', (done: MochaDone) => {
274-
const subject = new Subject();
298+
const subject = new Subject<string>();
275299
const expected = ['foo'];
276300

277301
subject.subscribe(function (x) {
@@ -397,7 +421,7 @@ describe('Subject', () => {
397421

398422
it('should be an Observer which can be given to Observable.subscribe', (done: MochaDone) => {
399423
const source = of(1, 2, 3, 4, 5);
400-
const subject = new Subject();
424+
const subject = new Subject<number>();
401425
const expected = [1, 2, 3, 4, 5];
402426

403427
subject.subscribe(
@@ -414,7 +438,7 @@ describe('Subject', () => {
414438

415439
it('should be usable as an Observer of a finite delayed Observable', (done: MochaDone) => {
416440
const source = of(1, 2, 3).pipe(delay(50));
417-
const subject = new Subject();
441+
const subject = new Subject<number>();
418442

419443
const expected = [1, 2, 3];
420444

@@ -431,7 +455,7 @@ describe('Subject', () => {
431455
});
432456

433457
it('should throw ObjectUnsubscribedError when emit after unsubscribed', () => {
434-
const subject = new Subject();
458+
const subject = new Subject<string>();
435459
subject.unsubscribe();
436460

437461
expect(() => {

spec/observables/from-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ describe('from', () => {
167167
expect(nextInvoked).to.equal(false);
168168
});
169169
it(`should accept a function`, (done) => {
170-
const subject = new Subject();
171-
const handler: any = (...args: any[]) => subject.next(...args);
170+
const subject = new Subject<any>();
171+
const handler: any = (arg: any) => subject.next(arg);
172172
handler[observable] = () => subject;
173173
let nextInvoked = false;
174174

spec/operators/bufferCount-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('bufferCount operator', () => {
4545
});
4646

4747
it('should buffer properly (issue #2062)', () => {
48-
const item$ = new Subject();
48+
const item$ = new Subject<number>();
4949
const results: any[] = [];
5050
item$.pipe(
5151
bufferCount(3, 1)

spec/operators/skipUntil-spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ describe('skipUntil', () => {
247247
const e1 = hot( '--a--b--c--d--e--|');
248248
const e1subs = ['^ !',
249249
'^ !']; // for the explicit subscribe some lines below
250-
const skip = new Subject();
250+
const skip = new Subject<string>();
251251
const expected = '-----------------|';
252252

253253
e1.subscribe((x: string) => {

src/internal/Subject.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class SubjectSubscriber<T> extends Subscriber<T> {
2525
*
2626
* @class Subject<T>
2727
*/
28-
export class Subject<T> extends Observable<T> implements SubscriptionLike {
28+
export class Subject<T = void> extends Observable<T> implements SubscriptionLike {
2929

3030
[rxSubscriberSymbol]() {
3131
return new SubjectSubscriber(this);
@@ -58,7 +58,7 @@ export class Subject<T> extends Observable<T> implements SubscriptionLike {
5858
return <any>subject;
5959
}
6060

61-
next(value?: T) {
61+
next(value: T) {
6262
if (this.closed) {
6363
throw new ObjectUnsubscribedError();
6464
}

src/internal/operators/repeatWhen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class RepeatWhenOperator<T> implements Operator<T, T> {
5959
*/
6060
class RepeatWhenSubscriber<T, R> extends OuterSubscriber<T, R> {
6161

62-
private notifications: Subject<any> | null = null;
62+
private notifications: Subject<void> | null = null;
6363
private retries: Observable<any> | null = null;
6464
private retriesSubscription: Subscription | null | undefined = null;
6565
private sourceIsBeingSubscribedTo: boolean = true;

src/internal/operators/share.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Subject } from '../Subject';
66
import { MonoTypeOperatorFunction } from '../types';
77

88
function shareSubjectFactory() {
9-
return new Subject();
9+
return new Subject<any>();
1010
}
1111

1212
/**

src/internal/operators/windowTime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ interface CloseState<T> {
165165
class CountedSubject<T> extends Subject<T> {
166166
private _numberOfNextedValues: number = 0;
167167

168-
next(value?: T): void {
168+
next(value: T): void {
169169
this._numberOfNextedValues++;
170170
super.next(value);
171171
}

0 commit comments

Comments
 (0)