Advanced RxJS Patterns in Angular

Advanced RxJS Patterns in Angular

RxJS is a powerful tool for handling reactive programming in Angular applications. Beyond basic observables, RxJS provides advanced patterns that optimize performance, enhance user experience, and simplify complex workflows.

In this guide, we’ll cover:

  1. Implementing Infinite Scrolling with RxJS
  2. Optimizing Performance with shareReplay
  3. Handling User Authentication Streams

 

1. Implementing Infinite Scrolling with RxJS

Infinite scrolling is a common UI pattern where more data loads automatically as the user scrolls. Instead of making multiple API requests unnecessarily, we can use RxJS with Intersection Observer for an optimized experience.

Step 1: Set Up a Service for API Calls

import { HttpClient } from '@angular/common/http';

import { Injectable } from '@angular/core';

import { Observable } from 'rxjs';

 

@Injectable({

  providedIn: 'root'

})

export class PostService {

  private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

 

  constructor(private http: HttpClient) {}

 

  getPosts(page: number, limit: number): Observable<any> {

    return this.http.get(`${this.apiUrl}?_page=${page}&_limit=${limit}`);

  }

}

 

Step 2: Implement Infinite Scrolling in a Component

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

import { PostService } from './post.service';

import { fromEvent, merge, of } from 'rxjs';

import { switchMap, scan, startWith } from 'rxjs/operators';

 

@Component({

  selector: 'app-infinite-scroll',

  template: `

    <div *ngFor="let post of posts">{{ post.title }}</div>

    <div #loadMore style="height: 50px; background: lightgray;">Loading...</div>

  `

})

export class InfiniteScrollComponent implements AfterViewInit {

  @ViewChild('loadMore', { static: false }) loadMore!: ElementRef;

  posts: any[] = [];

  currentPage = 1;

  limit = 10;

 

  constructor(private postService: PostService) {}

 

  ngAfterViewInit(): void {

    const observer = new IntersectionObserver(([entry]) => {

      if (entry.isIntersecting) {

        this.loadMorePosts();

      }

    });

 

    observer.observe(this.loadMore.nativeElement);

  }

 

  loadMorePosts() {

    this.postService.getPosts(this.currentPage++, this.limit).subscribe(data => {

      this.posts = [...this.posts, ...data];

    });

  }

}

Why This Works?

Optimized API Calls – Only fetches new data when needed.
Efficient Scrolling – Uses Intersection Observer instead of manually tracking scroll position.

 

2. Optimizing Performance with shareReplay

Repeated API calls for the same data can slow down your app. shareReplay caches the latest emitted values, preventing redundant requests.

Example: Caching API Calls with shareReplay

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable, shareReplay } from 'rxjs';

 

@Injectable({

  providedIn: 'root'

})

export class UserService {

  private userData$: Observable<any>;

 

  constructor(private http: HttpClient) {

    this.userData$ = this.http.get('https://api.example.com/user').pipe(

      shareReplay(1) // Caches the latest response

    );

  }

 

  getUser(): Observable<any> {

    return this.userData$;

  }

}

Usage in a Component

import { Component, OnInit } from '@angular/core';

import { UserService } from './user.service';

 

@Component({

  selector: 'app-user',

  template: `<div>{{ user$ | async | json }}</div>`

})

export class UserComponent implements OnInit {

  user$ = this.userService.getUser(); // Cached API response

 

  constructor(private userService: UserService) {}

 

  ngOnInit() {}

}


Why Use shareReplay(1)?

Prevents Duplicate API Calls – Only calls the API once, then shares the response.
Improves Performance – Reduces server load.
Ideal for Shared Data – Works great for user profiles, settings, or static data.

 

3. Handling User Authentication Streams

User authentication is often managed using RxJS Behaviors, ensuring a real-time authentication state across multiple components.

Step 1: Create an Authentication Service

import { Injectable } from '@angular/core';

import { BehaviorSubject, Observable } from 'rxjs';

 

@Injectable({

  providedIn: 'root'

})

export class AuthService {

  private authState = new BehaviorSubject<boolean>(false);

  authState$ = this.authState.asObservable(); // Expose Observable

 

  login() {

    // Simulate API login

    setTimeout(() => {

      this.authState.next(true);

    }, 1000);

  }

 

  logout() {

    this.authState.next(false);

  }

}

 

Step 2: Use Authentication Stream in a Component

import { Component } from '@angular/core';

import { AuthService } from './auth.service';

 

@Component({

  selector: 'app-auth',

  template: `

    <button *ngIf="!(auth$ | async)" (click)="login()">Login</button>

    <button *ngIf="auth$ | async" (click)="logout()">Logout</button>

  `

})

export class AuthComponent {

  auth$ = this.authService.authState$;

 

  constructor(private authService: AuthService) {}

 

  login() {

    this.authService.login();

  }

 

  logout() {

    this.authService.logout();

  }

}

Why Use RxJS for Authentication?

Real-time Authentication Updates – Components auto-update when login state changes.
Better State Management – Avoids manual event listeners.
Simplifies Authentication Logic – Centralizes state handling.

 

Conclusion

RxJS Pattern

Use Case

Infinite Scrolling with Intersection Observer

Optimized lazy loading of data

shareReplay for Performance Optimization

Prevents unnecessary API calls and caches responses

RxJS Authentication Streams

Manages login state reactively

 

RxJS provides powerful and efficient solutions for handling complex UI behaviors in Angular applications. By using these advanced patterns, you can create a faster, scalable, and more maintainable app.

Comments

Popular posts from this blog

Promises in Angular

Mastering Your Angular Workflow: Essential CLI Commands for Efficient Development

Observables in Angular