DRY Way to Manage Subscriptions in Angular Components
Tags: angular, rxjs, typescript
Published: 2021-09-28 12:10:26

*Cover photo by [Tim Swaan](https://unsplash.com/photos/eOpewngf68w) on Unsplash.*

This article explains how to manage subscriptions in Angular components without repeating the same teardown logic in each component.

## Common Ways

There are two common ways to manage RxJS subscriptions in Angular components to prevent memory leaks:

### Using Subscription

```ts
@Component({
  selector: 'interval',
  templateUrl: './interval.component.html',
})
export class IntervalComponent implements OnInit, OnDestroy {
  // initialize `Subscription` object
  private readonly subscriptions = new Subscription();

  ngOnInit(): void {
    // add all subscriptions to it
    this.subscriptions.add(
      interval(1000)
        .pipe(map(i => `== ${i} ==`))
        .subscribe(console.log)
    );

    this.subscriptions.add(
      interval(2000)
        .pipe(map(i => `=== ${i} ===`))
        .subscribe(console.log)
    );
  }

  ngOnDestroy(): void {
    // unsubscribe from all added subscriptions
    // when component is destroyed
    this.subscriptions.unsubscribe();
  }
}
```

### Using Destroy Subject

```ts
@Component({
  selector: 'interval',
  templateUrl: './interval.component.html',
})
export class IntervalComponent implements OnInit, OnDestroy {
  // initialize destroy subject
  private readonly destroySubject$ = new Subject<void>();

  ngOnInit(): void {
    interval(1000)
      .pipe(
        map(i => `== ${i} ==`),
        // unsubscribe when destroy subject emits an event
        takeUntil(this.destroySubject$)
      )
      .subscribe(console.log);

    interval(2000)
      .pipe(
        map(i => `=== ${i} ===`),
        takeUntil(this.destroySubject$)
      )
      .subscribe(console.log);
  }

  ngOnDestroy(): void {
    // emit destroy event when component is destroyed
    this.destroySubject$.next();
  }
}
```

Both solutions have the same drawback: We have to initialize the additional property, and add teardown logic to the `ngOnDestroy` method. However, there is a better way to manage subscriptions in Angular components.

## Solution

We can put the teardown logic in a single place by creating `Destroy` class that extends the `Observable` class and implements the `OnDestroy` interface:

```ts
@Injectable()
export class Destroy extends Observable<void> implements OnDestroy {
  // initialize destroy subject
  private readonly destroySubject$ = new ReplaySubject<void>(1);

  constructor() {
    // emit destroy event to all subscribers when destroy subject emits
    super(subscriber => this.destroySubject$.subscribe(subscriber));
  }

  ngOnDestroy(): void {
    // emit destroy event when component that injects
    // `Destroy` provider is destroyed
    this.destroySubject$.next();
    this.destroySubject$.complete();
  }
}
```

Then, we can provide `Destroy` at the component level and inject it through the constructor:

```ts
@Component({
  // provide `Destroy` at the component level
  viewProviders: [Destroy]
})
export class IntervalComponent implements OnInit {
  // inject it through the constructor
  constructor(private readonly destroy$: Destroy) {}

  ngOnInit(): void {
    interval(1000)
      .pipe(
        map(i => `== ${i} ==`),
        // unsubscribe when `destroy$` Observable emits an event
        takeUntil(this.destroy$)
      )
      .subscribe(console.log);
  }
}
```

When a provider is provided at the component level, it will be tied to the component lifecycle which allows us to use the `ngOnDestroy` lifecycle method within it. Therefore, the `ngOnDestroy` method of the `Destroy` provider will be called when the `IntervalComponent` is destroyed.

## Conclusion

In general, manual (un)subscriptions in Angular components should be avoided. If you need to perform a side effect at the component level, you can do so using the [`@ngrx/component-store`](https://ngrx.io/guide/component-store) effects, and let `ComponentStore` take care to prevent memory leaks. However, if you prefer to manage the side effects in the components, consider using the `Destroy` provider to avoid repeating the same teardown logic in each component.

## Peer Reviewers

- [Tim Deschryver](https://twitter.com/tim_deschryver)
- [Lars Gyrup Brink Nielsen](https://twitter.com/LayZeeDK)