Skip to main content

Overview

This guide documents the approved patterns for managing RxJS subscriptions in the CometChat Angular V5 UIKit. Following these patterns ensures that subscriptions are automatically cleaned up when components and services are destroyed, preventing memory leaks in long-running sessions.

✅ Approved Patterns

1. takeUntilDestroyed() — Primary Pattern

The preferred pattern for all new subscriptions. Uses Angular’s DestroyRef to automatically unsubscribe when the component or service is destroyed.
Why this is preferred:
  • No boilerplate ngOnDestroy needed for subscription cleanup
  • Works in both components and services
  • Angular manages the lifecycle automatically via DestroyRef
  • Composable with other RxJS operators

2. toSignal() — For Service Observables

Use toSignal() when you need to consume a service observable as an Angular Signal in a component. The subscription is automatically cleaned up when the component is destroyed.
Why this is preferred for service state:
  • Zero subscription management code
  • Integrates with Angular’s change detection (OnPush compatible)
  • Automatically handles initial value and completion

3. CometChat SDK Listeners — Service-Managed Pattern

CometChat SDK listeners (addMessageListener, addUserListener, etc.) must be registered and removed via the service layer, not directly in components.
Why services manage SDK listeners:
  • SDK listeners are singleton-scoped — they must persist across component destroy/recreate cycles (e.g., tab switching)
  • Components should never call cleanup() in ngOnDestroy — this would tear down listeners on every navigation
  • Call cleanup() only for application-level events: user logout, account switching

❌ Anti-Patterns to Avoid

❌ Manual destroy$ Subject

Problem: Requires manual ngOnDestroy implementation. If next() or complete() is forgotten, subscriptions leak. Fix: Use takeUntilDestroyed(this.destroyRef) instead.

❌ Manual Subscription.unsubscribe()

Problem: Each subscription requires a separate property and manual cleanup. Scales poorly. Fix: Use takeUntilDestroyed(this.destroyRef) — no property or ngOnDestroy needed.

❌ Subscribing Without Any Cleanup

Problem: The subscription persists after the component is destroyed, accumulating over time. Fix: Always use takeUntilDestroyed(this.destroyRef) or toSignal().

Migration Guide

If you encounter the old takeUntil(this.destroy$) pattern, here is how to migrate:

Before

After

Steps:
  1. Add DestroyRef to @angular/core imports
  2. Add takeUntilDestroyed to @angular/core/rxjs-interop imports
  3. Add private readonly destroyRef = inject(DestroyRef) field
  4. Replace .pipe(takeUntil(this.destroy$)) with .pipe(takeUntilDestroyed(this.destroyRef))
  5. Remove private destroy$ = new Subject<void>()
  6. Remove this.destroy$.next() and this.destroy$.complete() from ngOnDestroy
  7. Remove OnDestroy interface if no other cleanup is needed
  8. Remove unused Subject and takeUntil imports

Summary

PatternUse CaseCleanup
takeUntilDestroyed(destroyRef)Any RxJS subscription in component/serviceAutomatic via DestroyRef
toSignal(obs$)Service observable → Signal in componentAutomatic via DestroyRef
Service-managed SDK listenersCometChat SDK addXxxListenerManual via cleanup() on logout
takeUntil(destroy$)❌ DeprecatedManual — avoid
subscription.unsubscribe()❌ DeprecatedManual — avoid