Skip to content

Commit 56914f2

Browse files
authored
fix: unsubscribe only from actual subscriptions (#165)
1 parent 284ed60 commit 56914f2

File tree

4 files changed

+41
-19
lines changed

4 files changed

+41
-19
lines changed

libs/until-destroy/src/lib/internals.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import { Subject } from 'rxjs';
33

44
import { PipeType } from './ivy';
55

6-
export function isFunction(target: unknown) {
7-
return typeof target === 'function';
8-
}
9-
106
/**
117
* Applied to instances and stores `Subject` instance when
128
* no custom destroy method is provided.

libs/until-destroy/src/lib/until-destroy.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import {
22
InjectableType,
33
ɵComponentType as ComponentType,
4-
ɵDirectiveType as DirectiveType
4+
ɵDirectiveType as DirectiveType,
55
} from '@angular/core';
6-
import { SubscriptionLike } from 'rxjs';
6+
import { Subscription } from 'rxjs';
77

88
import { PipeType, isPipe } from './ivy';
99
import {
1010
getSymbol,
11-
isFunction,
1211
UntilDestroyOptions,
1312
completeSubjectOnTheInstance,
14-
markAsDecorated
13+
markAsDecorated,
1514
} from './internals';
1615

17-
function unsubscribe(property: SubscriptionLike | undefined): void {
18-
property && isFunction(property.unsubscribe) && property.unsubscribe();
16+
function unsubscribe(property: unknown): void {
17+
if (property instanceof Subscription) {
18+
property.unsubscribe();
19+
}
1920
}
2021

2122
function unsubscribeIfPropertyIsArrayLike(property: any[]): void {
@@ -26,7 +27,7 @@ function decorateNgOnDestroy(
2627
ngOnDestroy: (() => void) | null | undefined,
2728
options: UntilDestroyOptions
2829
) {
29-
return function(this: any) {
30+
return function (this: any) {
3031
// Invoke the original `ngOnDestroy` if it exists
3132
ngOnDestroy && ngOnDestroy.call(this);
3233

libs/until-destroy/src/lib/until-destroyed.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import { takeUntil } from 'rxjs/operators';
44
import {
55
DECORATOR_APPLIED,
66
getSymbol,
7-
isFunction,
87
createSubjectOnTheInstance,
9-
completeSubjectOnTheInstance
8+
completeSubjectOnTheInstance,
109
} from './internals';
1110

1211
// This will be provided through Terser global definitions by Angular CLI. This will
@@ -20,15 +19,15 @@ function overrideNonDirectiveInstanceMethod(
2019
): void {
2120
const originalDestroy = instance[destroyMethodName];
2221

23-
if (ngDevMode && isFunction(originalDestroy) === false) {
22+
if (ngDevMode && typeof originalDestroy !== 'function') {
2423
throw new Error(
2524
`${instance.constructor.name} is using untilDestroyed but doesn't implement ${destroyMethodName}`
2625
);
2726
}
2827

2928
createSubjectOnTheInstance(instance, symbol);
3029

31-
instance[destroyMethodName] = function() {
30+
instance[destroyMethodName] = function () {
3231
// eslint-disable-next-line prefer-rest-params
3332
originalDestroy.apply(this, arguments);
3433
completeSubjectOnTheInstance(this, symbol);

libs/until-destroy/tests/until-destroy.spec.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
ɵComponentDef as ComponentDef,
3-
ɵɵdefineComponent as defineComponent
3+
ɵɵdefineComponent as defineComponent,
44
} from '@angular/core';
55
import { interval, Subject } from 'rxjs';
66

@@ -17,7 +17,7 @@ describe('UntilDestroy decorator alone', () => {
1717
decls: 0,
1818
type: TestComponent,
1919
selectors: [[]],
20-
template: () => {}
20+
template: () => {},
2121
}) as ComponentDef<TestComponent>;
2222

2323
subscription = interval(1000).subscribe();
@@ -42,7 +42,7 @@ describe('UntilDestroy decorator alone', () => {
4242
decls: 0,
4343
type: TestComponent,
4444
selectors: [[]],
45-
template: () => {}
45+
template: () => {},
4646
}) as ComponentDef<TestComponent>;
4747

4848
intervalSubscription = interval(1000).subscribe();
@@ -74,7 +74,7 @@ describe('UntilDestroy decorator alone', () => {
7474
decls: 0,
7575
type: TestComponent,
7676
selectors: [[]],
77-
template: () => {}
77+
template: () => {},
7878
}) as ComponentDef<TestComponent>;
7979

8080
subscriptions = [interval(1000).subscribe(), new Subject().subscribe()];
@@ -95,4 +95,30 @@ describe('UntilDestroy decorator alone', () => {
9595
expect(subscription.closed).toBeTruthy();
9696
});
9797
});
98+
99+
it('should unsubscribe only from actual subscriptions', () => {
100+
// Arrange
101+
@UntilDestroy({ checkProperties: true })
102+
class TestComponent {
103+
static ɵcmp = defineComponent({
104+
vars: 0,
105+
decls: 0,
106+
type: TestComponent,
107+
selectors: [[]],
108+
template: () => {},
109+
}) as ComponentDef<TestComponent>;
110+
111+
subject$ = new Subject<never>();
112+
113+
static ɵfac = () => new TestComponent();
114+
}
115+
116+
// Act & assert
117+
const component = TestComponent.ɵfac();
118+
119+
expect(component.subject$.closed).toBe(false);
120+
callNgOnDestroy(component);
121+
// The `Subject` also has the `unsubscribe()` method, but it's not a subscription.
122+
expect(component.subject$.closed).toBe(false);
123+
});
98124
});

0 commit comments

Comments
 (0)