Why RxJS Feels Hard
RxJS is powerful because it models time and events. It feels complex when streams are created without clear ownership and lifecycle rules.
Architecture Guidelines
- Keep streams close to the feature boundary
- Expose read-only observables from services
- Encapsulate writes through explicit methods
- Prefer pure operators over imperative subscriptions
Example: Query Stream with Cancellation
results$ = this.query$.pipe(
debounceTime(250),
distinctUntilChanged(),
switchMap(query => this.api.search(query)),
shareReplay({ bufferSize: 1, refCount: true })
)
switchMap cancels stale requests automatically, which is ideal for search and filters.
State and Side Effects
Use separate streams for:
- View state (
loading,error,data) - User intent (button clicks, input changes)
- External effects (HTTP, router, analytics)
This separation prevents tangled operators and hidden coupling.
Memory Safety in Angular
- Use
takeUntilDestroyed()in components - Avoid nested subscriptions
- Keep subscriptions in templates via
asyncpipe when possible
Code Review Checklist
- Is there a clear source stream?
- Are cancellation semantics correct (
switchMap,exhaustMap,concatMap)? - Are errors mapped to state instead of terminating the stream unexpectedly?
- Is stream reuse safe (
shareReplay) without stale cache behavior?
Closing Takeaway
RxJS in Angular becomes scalable when streams are treated as domain contracts, not implementation details. Define intent, model lifecycle, and keep side effects explicit.


