• [RxJS] Just enough about ShareReplay


    When new to Reactive programming with Angular. It is easy to fall into a performance issue, which is sending multi same http request to the backend.

    In this post, we will first setup a basic Angular service - component, then try to understand the problem occurt behind the code: finally we will discuss the solution to solve the problem.

    Setup

    Let's say we have a service to fetch data:

    // categories.service.ts
    
    import {Injectable} from '@angular/core';
    import {HttpClient} from '@angular/common/http';
    import {Observable} from 'rxjs';
    import {map} from 'rxjs/operators';
    import {Category} from '../shared/model.ts'
    
    @Injectable({
      providedIn: 'root'
    })
    export class CategoriesService {
    
      constructor(private http: HttpClient) { }
    
      getAllCategories(): Observable<Course[]> {
        return this.http.get<Categories[]>('/api/categories')
          .pipe(
            map(response => response['payload'])
          );
      }
    }

    The logic for this CategoriesService is pretty simple, just a method 'getAllCategories'. 

    Now let's see the component code:

    @Component({
        selector: 'home',
        ...
    })
    export class HomeComponent implements OnInit {
    beginnerCategories$: Observable
    <Category[]>; advancedCategories$: Observable<Category[]>; constructor(private service: CategoriesService) {} ngOnInit() { const categories$ = this.service.getAllCategories(); this.beginnerCategories$ = categories$.pipe( map( cs => cs.filter(c => c.category === 'BEGINNER') ) ) this.advancedCategories$ = categories$.pipe( map( cs => cs.filter(c => c.category === 'ADVANCED') ) ) } }

    In the component, we call 'CategoriesService' to fetch all the data, then we create two other observables, one is for 'beginner' category, another is 'advanced' category.

    Then we use those observables inside our template, we use 'async' pipe here.

    <category-card-list
           [categories]="beginnerCategories$ | async">
    </category-card-list>
    
    <category-card-list
           [categories]="advancedCategories$ | async">
    </category-card-list>

    Understand the problem

    After running the applciaiton, open the devtool, check the network tab. you will find there are TWO '/api/categories' made to the backend. 

    Why is that? We only call 'getAllCategories' once inside our component, why it issue two requests?

    // component
    
      ngOnInit() {
          const categories$ = this.service.getAllCategories();
        
           ....
      }

    It is because how 'async' pipe works. 'async' pipe will automaticlly 'subscribe' to the observable. You can consider every time you use 'async' pipe, it will add one more subscription. Since inside template it uses two 'async' pipes, it means we call subscribe two times. 

    It is equivalent to this code:

      ngOnInit() {
        this.service.getAllCategories().pipe(
           map(cs => cs.filter(c => c.category === 'BEGINNER'))
        ).subscribe(
          cs => this.beginnerCategories = cs
        )
    
        this.service.getAllCategories().pipe(
           map(cs => cs.filter(c => c.category === 'ADVANCED'))
        ).subscribe(
          cs => this.advancedCategories = cs
        )
      }

    Because we didn't apply any cache to our service, it will issue two requests to the backend.

    Solution

    The solution is pretty simple, we just need to use 'shareReplay' inside service. It will share the same execution to the all subscribers.

    signature: shareReplay(bufferSize?: number, windowTime?: number, scheduler?I IScheduler): Observable

    // categories.service.ts
    ...
    import {map, shareReplay} from 'rxjs/operators';
    import {Category} from '../shared/model.ts'
    
    @Injectable({
      providedIn: 'root'
    })
    export class CategoriesService {
    
      constructor(private http: HttpClient) { }
    
      getAllCategories(): Observable<Course[]> {
        return this.http.get<Categories[]>('/api/categories')
          .pipe(
            map(response => response['payload']),
            shareReplay()
          );
      }
    }

    Now 'beginnerCategories$' and 'advancedCategories' shares the same execution of:

    this.service.getAllCategories();

    Best partices

    ShareReplay is good for preventing duplicate HTTP reqeusts. Therefore, for the most of 'GET' and 'PUT' request, we can add 'shareReplay'.

    //categories.service.ts
    
    @Injectable({providerIn> 'root'})
    export class CategoriesService {
    
      constructor(private http: HttpClient) {
    
      }
      getAllCategories(): Observable<Category[]> {
        return this.http.get<Category[]>('/api/categories')
          .pipe(
            map(response => response['payload']),
            shareReplay()
          );
      }
    
      loadCategoryById(cid: number) {
        return this.http.get<Category>(`/api/categories/${cid}`)
          .pipe(
            shareReplay()
          );
      }
    
      saveCategory(cid: string, changes: Partial<Category>): Observable<any> {
        return this.http.put(`/api/categories/${cid}`, changes)
          .pipe(
            map(response => response['payload']),
            shareReplay()
          );
      }
    
    }

    Summary

    In this post, we have see 'async' pipe might casues multi http request, and to prevent thsi problem we can use 'shareReplay' inside our service to prevent the problem. 

  • 相关阅读:
    ios 手写键盘闪退问题 UIKBBlurredKeyView candidateList
    ios NSURLErrorDomain Code=-1004 "未能连接到服务器。问题
    最牛B的编码套路
    watchOS开发—初步认识
    iOS开发拓展篇—蓝牙之CoreBlueTooth(BLE)
    iOS开发拓展篇—蓝牙之mutipeerConnectivity的使用
    iOS开发拓展篇—蓝牙之GameKit使用
    iOS开发拓展篇—ReactiveCocoa常见操作方法介绍(进阶篇)
    iOS开发拓展篇—ReactiveCocoa介绍(基础篇)
    iOS开发拓展篇—异常处理
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12422863.html
Copyright © 2020-2023  润新知