Skip to content

Commit 91b518c

Browse files
committed
Update documentation
1 parent 30cefde commit 91b518c

File tree

3 files changed

+399
-366
lines changed

3 files changed

+399
-366
lines changed

EXAMPLES.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# Examples with further explanations
2+
## How to run the examples by yourself
3+
The project was set up using the latest [Angular CLI](https://github.com/angular/angular-cli) (version 17).
4+
5+
To run the examples, cd into ``angular-app`` folder and install the Angular app with
6+
``
7+
npm install
8+
``.
9+
10+
Then start it using
11+
``
12+
npm start
13+
``.
14+
15+
## The questions
16+
We want to answer the following questions:
17+
18+
### 1. Can we run into the problem of unwanted side effects?
19+
To investigate this issue, we use the scenario that we want to set the document title (inside the component we navigated to)
20+
dynamically on behalf of a observable.
21+
To this end, we use the Angular title service (https://angular.io/guide/set-document-title).
22+
23+
In each experiment, we set the title in the observables callback inside component ``XY``, then navigate to the ``EmptyComponent``
24+
(which does nothing else than setting its own title), and observe if the title still gets updated.
25+
26+
### 2. Can we run into the problem of memory leaks?
27+
We navigate from the component ``XY`` with the observable (we want to study) to the ``EmptyComponent``.
28+
Under normal circumstances, the component ``XY`` should be garbage collected.
29+
However, it can happen that the component cannot be garbage collected due to references used in observable callbacks.
30+
31+
We will use Google Chrome's memory snapshot tool to see if the components get garbage collected or not.
32+
33+
34+
## Observables that don't complete
35+
The first and most trivial case is an observable that does not complete.
36+
37+
Say you have a timer observable in your component, like in our case study component ``RxjsTimerComponent``
38+
```
39+
this.subscription = timer(0, 1000)
40+
.subscribe(() => {
41+
this.counter++;
42+
this.titleService.setTitle('Counter ' + this.counter);
43+
});
44+
```
45+
This is an infinite timer observable, that executes the callback each second.
46+
On each emit, we increase a counter and set the document title with the title service.
47+
48+
### Outcomes
49+
#### Side effects
50+
As expected, after routing from the ``RxjsTimerComponent`` to the ``EmptyComponent``,
51+
the document title gets updated each second.
52+
So the observable still runs after the ``RxjsTimerComponent`` was destroyed.
53+
So we have an unwanted side effect in our example.
54+
55+
#### Memory leaks
56+
As expected, after routing to the ``EmptyComponent``, the ``RxjsTimerComponent`` is still in memory and doesn't
57+
get garbage collected. This makes sense, since there is still a reference to ``this.counter``
58+
and ``this.titleService`` in the observables callback method.
59+
60+
In fact, by navigating back and forth between ``EmptyComponent`` and ``RxjsTimerComponent`` we can create many
61+
``RxjsTimerComponent`` objects that don't get cleaned up. Thus we indeed created a memory leak in our example.
62+
63+
NOTE: If you don't use any references (like member variables) from the component in the callback,
64+
the component gets garbage collected and there is no memory leak.
65+
66+
### Summary
67+
__ALWAYS unsubscribe if the observable does not complete or if you are not sure if it completes,
68+
since the callback logic still runs in the background otherwise,
69+
possibly creating memory leaks and unwanted side effects.__
70+
71+
## Observables that eventually complete
72+
We created the component ``RxjsTimerComplete``. It contains the following observable:
73+
```
74+
this.subscription = timer(0, 1000)
75+
.pipe(take(5))
76+
.subscribe(() => {
77+
this.counter++;
78+
this.titleService.setTitle('Counter ' + this.counter);
79+
});
80+
```
81+
We used a pipe with ``take(5)``, thus the observable we subscribe to takes only
82+
the first 5 values from the source and then completes.
83+
84+
Again, we increase a counter on each emit and set
85+
the document title with the title service.
86+
87+
### Outcomes
88+
#### Side effects
89+
If we navigate, before the last value is emitted (i.e. within the first 5 seconds), we can observe
90+
that the document title is still updating. Thus we have an unwanted side effect as in the first case study,
91+
as long as the observable is not complete yet.
92+
93+
Of course there is _no_ unwanted side effect _in our example_, if we wait and navigate after the first 5 values were emitted and
94+
the observable completed.
95+
96+
#### Memory leak
97+
No matter if we navigate before the observable completes or afterwards -
98+
the component ``RxjsTimerComplete`` always eventually gets garbage collected after it completed.
99+
So there is no memory leak.
100+
101+
### Summary
102+
__ALWAYS unsubscribe if you execute code with side effects in your callback.__
103+
104+
## Observables from the Angular HttpClient
105+
Let's have a look at the angular source code to understand how an observable created from the HttpClient works.
106+
```
107+
if (ok) {
108+
observer.next(new HttpResponse({
109+
// ... code omitted ...
110+
}));
111+
112+
observer.complete();
113+
} else {
114+
observer.error(new HttpErrorResponse({
115+
// ... code omitted ...
116+
}));
117+
}
118+
```
119+
The important part is ``observer.complete()`` in successful call and ``observer.error(...)`` in error case,
120+
which means the observable has finished and does not emit any more values.
121+
122+
This means that the same findings from the case study [Observables that eventually complete](#Observables that eventually complete) apply.
123+
124+
Why? Since network calls can be arbitrarily delayed, the observable can complete when you already navigated to another component.
125+
126+
### Investigation method
127+
We added a little server written in Go under the folder ``go-server`` to the project,
128+
such that the effect of delayed network calls can be demonstrated.
129+
130+
Furthermore we added the component ``HttpclientComponent`` which issues a GET call to the API exposed by the Go Server.
131+
The GET call delays the response by 5 seconds and then returns an object with a title property.
132+
The component sets the document title with the received data:
133+
134+
```
135+
this.subscription = this.httpClient.get<ApiResponse>('/api').subscribe((result) => {
136+
this.title = result.title;
137+
this.titleService.setTitle(result.title);
138+
});
139+
```
140+
141+
### Outcomes
142+
As already mentioned, we observe the same outcome as with an observable that eventually completes.
143+
144+
#### Side effects
145+
Are possible due to network latencies. The callback still gets executed, even if the user already navigated to another component.
146+
147+
#### Memory leak
148+
After the call completes the component gets garbage collected (even if navigated to another component).
149+
150+
### Summary
151+
__ALWAYS unsubscribe if you execute code with side effects in your callback.__
152+
Many get that wrong, since they have almost no latency in their network calls when testing their application.
153+
If you do not execute methods with unwanted side effects, you do not have to unsubscribe.
154+
Memory leaks are avoided by the HttpClient as the observable completes itself.
155+
156+
## Angular routing: ParamMap and QueryParamMap (ActivatedRoute) observables
157+
The Angular documentation already gives the answer if we should unsubscribe in this case:
158+
159+
> When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.
160+
>
161+
> There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions.
162+
>
163+
> The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.
164+
> Feel free to unsubscribe anyway. It is harmless and never a bad practice.
165+
166+
We created the component ``RouterParamMapComponent`` to verify this by an example.
167+
The example used the following code (for the queryParamMap) in the ``ngOnInit()`` method.
168+
```
169+
this.activatedRoute.queryParamMap.subscribe((queryParamMap) => {
170+
this.queryParamMap = queryParamMap;
171+
this.titleService.setTitle(queryParamMap.get('queryParam'));
172+
});
173+
```
174+
175+
### Outcomes and summary
176+
The component gets garbage collected (after the second navigation from the component, not the first) and no side-effects
177+
were noticed. The callback is not executed anymore after navigated away from the component.
178+
179+
So the statement from the documentation is correct and you don't have to manually unsubscribe when using observables
180+
from ``ActivatedRoute``.
181+
182+
NOTE: This is only valid for ``ActivatedRoute`` observables and not for other router observables (as shown below)!
183+
184+
185+
## Angular routing: Router events (NavigationStart, etc...)
186+
We created the component ``RouterEventsComponent`` with the following subscription in ``ngOnInit``:
187+
```
188+
this.router.events.subscribe((event) => {
189+
console.log('routerEvent', event);
190+
this.event = event;
191+
});
192+
```
193+
To investigate how this observable behaves, we navigated to the component and then navigated to other components afterwards.
194+
195+
### Outcomes
196+
#### Side effects
197+
The observables callback is still executed even when the component is destroyed. So there can be unwanted side effects,
198+
when using certain code in the callback.
199+
200+
#### Memory leak
201+
The above version creates a memory leak, since we use a reference to the component in the callback.
202+
Each time we navigate to the component a new dangling component (that cannot be garbage collected) is created.
203+
If no reference is used, the component gets garbage collected.
204+
205+
### Summary
206+
You should always unsubscribe when using the events observables from the ``Router`` in components
207+
(there is one exception tough, namely if you use it in the root component) as the subscription is still alive, even if
208+
the component was destroyed by Angular.
209+
210+
## Memory leaks in component trees
211+
We wondered if a subcomponent of a component with an observable that does not complete could maybe
212+
cause a memory leak on the whole component tree upwards.
213+
214+
To this end we created ``ComponentTreeComponent``, which has an infinite time observable in the most inner
215+
component.
216+
217+
### Outcomes
218+
As expected, only the component with the timer does not get garbage collected.
219+
This is good news, as this means the third party components (as used as in this example)
220+
cannot affect memory leaks on your components.
221+
222+
223+
# Other resources
224+
### Angular/RxJs When should I unsubscribe from Subscription:
225+
https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription
226+
227+
### Is it necessary to unsubscribe from observables created by Http methods?
228+
https://stackoverflow.com/questions/35042929/is-it-necessary-to-unsubscribe-from-observables-created-by-http-methods

0 commit comments

Comments
 (0)