Skip to content

Commit acf91ea

Browse files
committed
Update text
1 parent e2a7bd7 commit acf91ea

File tree

1 file changed

+125
-147
lines changed

1 file changed

+125
-147
lines changed

README.md

Lines changed: 125 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This repository provides a guide explaining when you have to unsubscribe manuall
2525
Whether you have to unsubscribe or not depends on the callback logic you are using in the observables subscription.
2626

2727
### Side effects
28-
If the callback code from the subscription executes code with side effects (e.g. affecting global application state)
28+
If the callback code from the subscription executes code with side effects (affecting global application state)
2929
you should always consider to manually unsubscribe when the component gets destroyed.
3030
Otherwise the current and correct application state can be falsely overwritten by callback execution from a destroyed component.
3131

@@ -62,7 +62,126 @@ When you should unsubscribe when the component gets destroyed.
6262
(3): Assuming the observable completes.
6363
(4): You don't have to, but are free to unsubscribe anyway.
6464

65-
# Further explanation and examples
65+
66+
# Recommended ways to unsubscribe
67+
One way is to assign the subscription to a class property and manually unsubscribe in ``ngOnDestroy``.
68+
```
69+
ngOnDestroy() {
70+
// Avoid side effects and memory leak by unsubscribing:
71+
this.subscription.unsubscribe();
72+
}
73+
```
74+
75+
However, its not feasible for large applications with many subscriptions,
76+
as its cumbersome to write and introduces a lot of obfuscating code.
77+
78+
Another way of handling it, would be to collect all subscriptions and unsubscribe them at once:
79+
```
80+
private readonly subscription = new Subscription();
81+
82+
everySecond = timer(0, 1000);
83+
everyThirdSecond = timer(0, 3000);
84+
85+
constructor() {
86+
}
87+
88+
ngOnInit() {
89+
this.subscription.add(this.everySecond.subscribe(() => {
90+
// some logic here
91+
}));
92+
this.subscription.add(this.everyThirdSecond.subscribe(() => {
93+
// some logic here
94+
}));
95+
}
96+
97+
ngOnDestroy() {
98+
this.subscription.unsubscribe();
99+
}
100+
```
101+
However, same drawbacks here: Cumbersome and obfuscating.
102+
103+
## Unsubscribe with ``takeUntil``
104+
A cleaner way of unsubscribing is using ``takeUntil``.
105+
106+
Official docs for ``takeUntil``: ``takeUntil(notifier: Observable<any>)`` — Emits the values emitted by the source Observable until a notifier Observable emits a value.
107+
108+
Example code:
109+
```
110+
private readonly ngDestroy = new Subject();
111+
112+
everySecond = timer(0, 1000);
113+
everyThirdSecond = timer(0, 3000);
114+
115+
constructor() {
116+
}
117+
118+
ngOnInit() {
119+
this.everySecond.pipe(takeUntil(this.ngDestroy))
120+
.subscribe(() => {
121+
// some logic here
122+
});
123+
124+
this.everyThirdSecond.pipe(takeUntil(this.ngDestroy))
125+
.subscribe(() => {
126+
// some logic here
127+
});
128+
}
129+
130+
ngOnDestroy() {
131+
this.ngDestroy.next();
132+
this.ngDestroy.complete();
133+
}
134+
```
135+
When the component gets destroyed, the observable ``ngDestroy`` gets completed, causing the subscriptions to complete.
136+
Thus memory leaks and side effects are avoided.
137+
138+
Drawbacks: As with the other methods, it's still quite verbose and error-prone.
139+
140+
NOTE: takeUntil operator should always come last (https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef)
141+
142+
## Using ``untilDestroyed``
143+
There is an npm package called ``@ngneat/until-destroy`` (https://github.com/ngneat/until-destroy).
144+
145+
You can install it (for Angular versions using Ivy) with
146+
```
147+
npm install --save @ngneat/until-destroy
148+
```
149+
150+
Or for previous Angular versions with:
151+
```
152+
npm install --save ngx-take-until-destroy
153+
```
154+
155+
It simplifies the unsubscription handling a lot. It internally creates a ``Subject`` and uses ``takeUntil``, hooked into ``ngOnDestroy``.
156+
The above code simplifies to:
157+
```
158+
everySecond = timer(0, 1000);
159+
everyThirdSecond = timer(0, 3000);
160+
161+
constructor() {
162+
}
163+
164+
ngOnInit() {
165+
this.everySecond.pipe(untilDestroyed(this))
166+
.subscribe(() => {
167+
// some logic here
168+
});
169+
170+
this.everyThirdSecond.pipe(untilDestroyed(this))
171+
.subscribe(() => {
172+
// some logic here
173+
});
174+
}
175+
176+
ngOnDestroy() {
177+
// needed for untilDestroyed
178+
}
179+
```
180+
Only drawback: We have to implement ``ngOnDestroy`` everywhere we want to use ``untilDestroyed``.
181+
However it should be easy to write a linting rule to enforce this.
182+
183+
184+
# Examples with further explanations
66185
## How to run the examples by yourself
67186
The project was set up using the latest [Angular CLI](https://github.com/angular/angular-cli) (version 8.3.6). Therefore you can just clone the repository and run
68187
``
@@ -74,15 +193,15 @@ angular-app
74193
``
75194
folder.
76195

77-
## The questions and the study method
78-
We want to answer the following questions in each case study:
196+
## The questions
197+
We want to answer the following questions:
79198

80199
### 1. Can we run into the problem of unwanted side effects?
81-
To investigate this issue, we use the scenario, that we want to set the document title (inside the component we navigated to)
200+
To investigate this issue, we use the scenario that we want to set the document title (inside the component we navigated to)
82201
dynamically on behalf of a observable.
83202
To this end, we use the Angular title service (https://angular.io/guide/set-document-title).
84203

85-
In each experiment, we set the title in the observables callback inside component ``XY``, navigate to the ``EmptyComponent``
204+
In each experiment, we set the title in the observables callback inside component ``XY``, then navigate to the ``EmptyComponent``
86205
(which does nothing else than setting its own title), and observe if the title still gets updated.
87206

88207
### 2. Can we run into the problem of memory leaks?
@@ -125,16 +244,6 @@ In fact, by navigating back and forth between ``EmptyComponent`` and ``RxjsTimer
125244
NOTE: If you don't use any references (like member variables) from the component in the callback,
126245
the component gets garbage collected and there is no memory leak.
127246

128-
### Countermeasures
129-
One countermeasure (among others at the end of this readme) is to manually unsubscribe in ``ngOnDestroy``.
130-
```
131-
ngOnDestroy() {
132-
// Avoid side effects and memory leak by unsubscribing:
133-
this.subscription.unsubscribe();
134-
}
135-
```
136-
With this statement, the side effects and the memory leak are avoided.
137-
138247
### Summary
139248
__ALWAYS unsubscribe if the observable does not complete or if you are not sure if it completes,
140249
since the callback logic still runs in the background otherwise,
@@ -170,16 +279,6 @@ No matter if we navigate before the observable completes or afterwards -
170279
the component ``RxjsTimerComplete`` always eventually gets garbage collected after it completed.
171280
So there is no memory leak.
172281

173-
### Countermeasures
174-
One countermeasure to avoid possible side effects (among others at the end of this readme) is to manually unsubscribe in ``ngOnDestroy``.
175-
```
176-
ngOnDestroy() {
177-
// Avoid side effects by unsubscribing:
178-
this.subscription.unsubscribe();
179-
}
180-
```
181-
With this statement, the side effects and the memory leak are avoided.
182-
183282
### Summary
184283
__ALWAYS unsubscribe if you execute code with side effects in your callback.__
185284

@@ -229,16 +328,6 @@ Are possible due to network latencies. The callback still gets executed, even if
229328
#### Memory leak
230329
After the call completes the component gets garbage collected (even if navigated to another component).
231330

232-
### Countermeasures
233-
One countermeasure to avoid possible side effects (amongst others at the end of this readme) is to manually unsubscribe in ``ngOnDestroy``.
234-
```
235-
ngOnDestroy() {
236-
// Avoid side effects by unsubscribing:
237-
this.subscription.unsubscribe();
238-
}
239-
```
240-
With this statement, the side effects and the memory leak are avoided.
241-
242331
### Summary
243332
__ALWAYS unsubscribe if you execute code with side effects in your callback.__
244333
Many get that wrong, since they have almost no latency in their network calls when testing their application.
@@ -311,117 +400,6 @@ As expected, only the component with the timer does not get garbage collected.
311400
This is good news, as this means the third party components (as used as in this example)
312401
cannot affect memory leaks on your components.
313402

314-
# Recommended ways to unsubscribe
315-
The obvious way of unsubscribing is how it is done in our examples: Assign the subscription to a class
316-
property and unsubscribe in the ``ngOnDestroy()`` method.
317-
318-
However, its not feasible for large applications with many subscriptions,
319-
as its cumbersome to write and introduces a lot of obfuscating code.
320-
321-
Another way of handling it, would be to collect all subscriptions and unsubscribe them at once:
322-
```
323-
private readonly subscription = new Subscription();
324-
325-
everySecond = timer(0, 1000);
326-
everyThirdSecond = timer(0, 3000);
327-
328-
constructor() {
329-
}
330-
331-
ngOnInit() {
332-
this.subscription.add(this.everySecond.subscribe(() => {
333-
// some logic here
334-
}));
335-
this.subscription.add(this.everyThirdSecond.subscribe(() => {
336-
// some logic here
337-
}));
338-
}
339-
340-
ngOnDestroy() {
341-
this.subscription.unsubscribe();
342-
}
343-
```
344-
However, same drawbacks here: Cumbersome and obfuscating.
345-
346-
## Unsubscribe with ``takeUntil``
347-
A cleaner way of unsubscribing is using ``takeUntil``.
348-
349-
Official docs for ``takeUntil``: ``takeUntil(notifier: Observable<any>)`` — Emits the values emitted by the source Observable until a notifier Observable emits a value.
350-
351-
Example code:
352-
```
353-
private readonly ngDestroy = new Subject();
354-
355-
everySecond = timer(0, 1000);
356-
everyThirdSecond = timer(0, 3000);
357-
358-
constructor() {
359-
}
360-
361-
ngOnInit() {
362-
this.everySecond.pipe(takeUntil(this.ngDestroy))
363-
.subscribe(() => {
364-
// some logic here
365-
});
366-
367-
this.everyThirdSecond.pipe(takeUntil(this.ngDestroy))
368-
.subscribe(() => {
369-
// some logic here
370-
});
371-
}
372-
373-
ngOnDestroy() {
374-
this.ngDestroy.next();
375-
this.ngDestroy.complete();
376-
}
377-
```
378-
When the component gets destroyed, the observable ``ngDestroy`` gets completed, causing the subscriptions to complete.
379-
Thus memory leaks and side effects are avoided.
380-
381-
Drawbacks: As with the other methods, it's still quite verbose and error-prone.
382-
383-
NOTE: takeUntil operator should always come last (https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef)
384-
385-
## Using ``untilDestroyed``
386-
There is an npm package called ``@ngneat/until-destroy`` (https://github.com/ngneat/until-destroy).
387-
388-
You can install it (for Angular versions using Ivy) with
389-
```
390-
npm install --save @ngneat/until-destroy
391-
```
392-
393-
Or for previous Angular versions with:
394-
```
395-
npm install --save ngx-take-until-destroy
396-
```
397-
398-
It simplifies the unsubscription handling a lot. It internally creates a ``Subject`` and uses ``takeUntil``, hooked into ``ngOnDestroy``.
399-
The above code simplifies to:
400-
```
401-
everySecond = timer(0, 1000);
402-
everyThirdSecond = timer(0, 3000);
403-
404-
constructor() {
405-
}
406-
407-
ngOnInit() {
408-
this.everySecond.pipe(untilDestroyed(this))
409-
.subscribe(() => {
410-
// some logic here
411-
});
412-
413-
this.everyThirdSecond.pipe(untilDestroyed(this))
414-
.subscribe(() => {
415-
// some logic here
416-
});
417-
}
418-
419-
ngOnDestroy() {
420-
// needed for untilDestroyed
421-
}
422-
```
423-
Only drawback: We have to implement ``ngOnDestroy`` everywhere we want to use ``untilDestroyed``.
424-
However it should be easy to write a linting rule to enforce this.
425403

426404
# Other resources
427405
### Angular/RxJs When should I unsubscribe from Subscription:

0 commit comments

Comments
 (0)