Lately I just saw an Angular project, that reminded my of an old project, that was written with an overly complex architecture. Async pipes for HttpClient calls in HTML, AbstractWhatEverFacades, linting rules from hell and over-bloated-I-import-everything-modules. So I asked them why they implemented it this way.

I will not discuss all answers, but the answer for async pipes for HttpClient calls in HTML made me curios: "Best practice against memory leaks by missing to unsubscribe". Which questioned my knowledge about how the HttpClient works.

I did some research and stumbled over statements like "Never subscribe manually", without saying the reason why, or "you have to unsubscribe every subscription", which is not wrong in the core. Some even said: "you must use the async pipe to program reactive". Okay, just program reactive to program reactive. Awesome reason. As a developer, it's not a good idea to do things, just to do things. Your job is to solve problems, not to show off some programming patterns you know. Nothing against them. Programming patterns are tools to solve problems in a proven way. They also give us orientation and security. But like with everything, we have to find a balance with them and see where they really make sense.

Now you could ask, what is my problem with async pipes? It's a nice tool if you have one async value to display and don't have to handle error or loading states. Is this a usual case? For me not. You mostly set an error, loading or for tables even more values. Imagine all the async pipes and the pipe(...) logic to handle for every value independently that are actually connected. You now have multiple subscriptions and logic over-bloating component. Have fun to implement filters and search with async pipes.

Now the good part! There is no need to unsubscribe HttpClient methods manually, because HttpClient provides a so called short-lived-observable. Means, GET, PUT, POST and DELETE will complete the observable after getting the first value. complete() unsubscribes all subscriptions of its observable automatically. So, you don't get memory leaks because of open subscriptions. You can test it like this:

1import { Injectable } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3
4@Injectable()
5export class BestService {
6
7  private url = "https://my.awesome.backend.com/api/cats";
8
9  constructor(private readonly http: HttpClient) {}
10
11  public getCats() {
12    return this.http.get(this.url).subscribe({
13      next: console.info,
14      error: console.error,
15      complete: () => console.info("completed")
16    });
17  }
18}
1import { Injectable } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3
4@Injectable()
5export class BestService {
6
7  private url = "https://my.awesome.backend.com/api/cats";
8
9  constructor(private readonly http: HttpClient) {}
10
11  public getCats() {
12    return this.http.get(this.url).subscribe({
13      next: console.info,
14      error: console.error,
15      complete: () => console.info("completed")
16    });
17  }
18}

What is about ongoing or so called long-lived-observables? I would recommend extending your component with a class like this:

1import { Injectable, OnDestroy } from '@angular/core';
2import { Subject } from 'rxjs';
3
4@Injectable()
5export abstract class UnsubOnDestroy implements OnDestroy {
6  public destroyed = new Subject<void>();
7  public ngOnDestroy(): void {
8    this.destroyed.next();
9    this.destroyed.complete();
10  }
11}
1import { Injectable, OnDestroy } from '@angular/core';
2import { Subject } from 'rxjs';
3
4@Injectable()
5export abstract class UnsubOnDestroy implements OnDestroy {
6  public destroyed = new Subject<void>();
7  public ngOnDestroy(): void {
8    this.destroyed.next();
9    this.destroyed.complete();
10  }
11}

And write your subscription like this:

1MySubject.pipe(takeUntil(this.destroyed)).subscribe(...); 
1MySubject.pipe(takeUntil(this.destroyed)).subscribe(...); 

So you unsubscribe on destroy without rewrite the logic again and again.

In conclusion: Don't be afraid to subscribe manually if it makes your life easier and your code more readable.