The old way to remove subscriptions in a component — a destroy$ Subject + takeUntil(destroy$) + destroy$.next() in ngOnDestroy. Modern Angular gives takeUntilDestroyed() — the same thing, but without the boilerplate, tied to DestroyRef.
The problem it solves
Subscriptions to intervals, DOM events, Subjects, and WebSockets don't complete on their own. If the component is destroyed but the subscription lives on, it pokes a DOM that's gone and holds references to deleted objects. A memory leak and console errors.
Operators and why they matter
takeUntil(destroy$) — the classic way. Each subscription unsubscribes when destroy$ emits.
finalize — shows the moment of teardown, handy for logs and debugging.
takeUntilDestroyed() — the modern equivalent. Takes the destroy signal from Angular's DestroyRef automatically.
Gotchas
HTTP one-shots complete on their own, but that doesn't handle cleanup for valueChanges, interval, Router events, and WebSockets — those are forever.
A manual destroy$ needs both next() and complete() in ngOnDestroy. Forget complete and the Subject keeps subscribers alive in tests.
takeUntilDestroyed() outside an injection context (outside a constructor/factory) needs an explicit DestroyRef argument.
What you get
Subscriptions close at destroy time automatically. Less boilerplate, less chance to forget.