Before we start, let’s make it clear: Manual subscription in the components’ lifecycle hooks is the worst way to handle observables. If there is no other option and using the async pipe doesn’t work as expected, then you can keep reading to learn how to safely achieve this and avoid zombie subscriptions around your application that will eventually lead to unexpected memory leaks.

A Real-World Case

For our example, I chose to implement a directive that logs its host element events to some user analytics provider. For simplicity, I will not describe how the service was implemented but rather focus on the events registration in our directive.

So, let’s see what the directive looks like:

/**
 * Supported events for analytics.
 */
export type AnalyticsEvent = 'click' | 'focus' | 'blur';

/**
 * Directive that used to log client events to the analytics provider by using {@link AnalyticsService}.
 */
@Directive({ selector: '[analytics]' })
export class AnalyticsDirective implements OnChanges {
  /**
   * List of supported events for analytics.
   */
  @Input()
  readonly events: AnalyticsEvent[];

  /**
   * Set of properties to attach to the logged events.
   */
  @Input()
  readonly properties: Record<string, Primitive> = {};

  constructor(
    private readonly elementRef: ElementRef,
    private readonly analyticsService: AnalyticsService
  ) {}

  ngOnChanges({ events }: SimpleChanges) {
    if (events) {
      this.registerEvents(this.events)
        .pipe(untilDestroyed(this))
        .subscribe(event => this.logEvent(event, this.properties));
    }
  }

  logEvent(event: Event, properties: Record<string, Primitive>) {
    this.analyticsService.logEvent({type: event.type, ...properties});
  }

  registerEvents(events: AnalyticsEvent[]): Observable<Event> {
    const { nativeElement: element } = this.elementRef;
    const eventsObservables = events.map(event => fromEvent<Event>(element, event));

    return merge(...eventsObservables);
  }
}

#reactive-programming #angular #rxjs #javascript #programming

Subscribing to Observables in Ongoing Angular Lifecycle Hooks
1.30 GEEK