What is the correct way to share the result of an Angular Http network call in RxJs 5?

前端 未结 21 1281
广开言路
广开言路 2020-11-21 06:11

By using Http, we call a method that does a network call and returns an http observable:

getCustomer() {
    return          


        
相关标签:
21条回答
  • 2020-11-21 06:23

    Cache the data and if available cached, return this otherwise make the HTTP request.

    import {Injectable} from '@angular/core';
    import {Http, Headers} from '@angular/http';
    import {Observable} from 'rxjs/Observable';
    import 'rxjs/add/observable/of'; //proper way to import the 'of' operator
    import 'rxjs/add/operator/share';
    import 'rxjs/add/operator/map';
    import {Data} from './data';
    
    @Injectable()
    export class DataService {
      private url: string = 'https://cors-test.appspot.com/test';
    
      private data: Data;
      private observable: Observable<any>;
    
      constructor(private http: Http) {}
    
      getData() {
        if(this.data) {
          // if `data` is available just return it as `Observable`
          return Observable.of(this.data); 
        } else if(this.observable) {
          // if `this.observable` is set then the request is in progress
          // return the `Observable` for the ongoing request
          return this.observable;
        } else {
          // example header (not necessary)
          let headers = new Headers();
          headers.append('Content-Type', 'application/json');
          // create the request, store the `Observable` for subsequent subscribers
          this.observable = this.http.get(this.url, {
            headers: headers
          })
          .map(response =>  {
            // when the cached data is available we don't need the `Observable` reference anymore
            this.observable = null;
    
            if(response.status == 400) {
              return "FAILURE";
            } else if(response.status == 200) {
              this.data = new Data(response.json());
              return this.data;
            }
            // make it shared so more than one subscriber can get the result
          })
          .share();
          return this.observable;
        }
      }
    }
    

    Plunker example

    This article https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html is a great explanation how to cache with shareReplay.

    0 讨论(0)
  • 2020-11-21 06:23

    Just use this cache layer, it does everything you requires, and even manage cache for ajax requests.

    http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html

    It's this much easy to use

    @Component({
        selector: 'home',
        templateUrl: './html/home.component.html',
        styleUrls: ['./css/home.component.css'],
    })
    export class HomeComponent {
        constructor(AjaxService:AjaxService){
            AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
        }
    
        articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
    }
    

    The layer(as an inject-able angular service) is

    import { Injectable }     from '@angular/core';
    import { Http, Response} from '@angular/http';
    import { Observable }     from 'rxjs/Observable';
    import './../rxjs/operator'
    @Injectable()
    export class AjaxService {
        public data:Object={};
        /*
        private dataObservable:Observable<boolean>;
         */
        private dataObserver:Array<any>=[];
        private loading:Object={};
        private links:Object={};
        counter:number=-1;
        constructor (private http: Http) {
        }
        private loadPostCache(link:string){
         if(!this.loading[link]){
                   this.loading[link]=true;
                   this.links[link].forEach(a=>this.dataObserver[a].next(false));
                   this.http.get(link)
                       .map(this.setValue)
                       .catch(this.handleError).subscribe(
                       values => {
                           this.data[link] = values;
                           delete this.loading[link];
                           this.links[link].forEach(a=>this.dataObserver[a].next(false));
                       },
                       error => {
                           delete this.loading[link];
                       }
                   );
               }
        }
    
        private setValue(res: Response) {
            return res.json() || { };
        }
    
        private handleError (error: Response | any) {
            // In a real world app, we might use a remote logging infrastructure
            let errMsg: string;
            if (error instanceof Response) {
                const body = error.json() || '';
                const err = body.error || JSON.stringify(body);
                errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
            } else {
                errMsg = error.message ? error.message : error.toString();
            }
            console.error(errMsg);
            return Observable.throw(errMsg);
        }
    
        postCache(link:string): Observable<Object>{
    
             return Observable.create(observer=> {
                 if(this.data.hasOwnProperty(link)){
                     observer.next(this.data[link]);
                 }
                 else{
                     let _observable=Observable.create(_observer=>{
                         this.counter=this.counter+1;
                         this.dataObserver[this.counter]=_observer;
                         this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
                         _observer.next(false);
                     });
                     this.loadPostCache(link);
                     _observable.subscribe(status=>{
                         if(status){
                             observer.next(this.data[link]);
                         }
                         }
                     );
                 }
                });
            }
    }
    
    0 讨论(0)
  • 2020-11-21 06:23

    Have you tried running the code you already have?

    Because you are constructing the Observable from the promise resulting from getJSON(), the network request is made before anyone subscribes. And the resulting promise is shared by all subscribers.

    var promise = jQuery.getJSON(requestUrl); // network call is executed now
    var o = Rx.Observable.fromPromise(promise); // just wraps it in an observable
    o.subscribe(...); // does not trigger network call
    o.subscribe(...); // does not trigger network call
    // ...
    
    0 讨论(0)
  • 2020-11-21 06:25

    Per @Cristian suggestion, this is one way that works well for HTTP observables, that only emit once and then they complete:

    getCustomer() {
        return this.http.get('/someUrl')
            .map(res => res.json()).publishLast().refCount();
    }
    
    0 讨论(0)
  • 2020-11-21 06:26

    Just call share() after map and before any subscribe.

    In my case, I have a generic service (RestClientService.ts) who is making the rest call, extracting data, check for errors and returning observable to a concrete implementation service (f.ex.: ContractClientService.ts), finally this concrete implementation returns observable to de ContractComponent.ts, and this one subscribe to update the view.

    RestClientService.ts:

    export abstract class RestClientService<T extends BaseModel> {
    
          public GetAll = (path: string, property: string): Observable<T[]> => {
            let fullPath = this.actionUrl + path;
            let observable = this._http.get(fullPath).map(res => this.extractData(res, property));
            observable = observable.share();  //allows multiple subscribers without making again the http request
            observable.subscribe(
              (res) => {},
              error => this.handleError2(error, "GetAll", fullPath),
              () => {}
            );
            return observable;
          }
    
      private extractData(res: Response, property: string) {
        ...
      }
      private handleError2(error: any, method: string, path: string) {
        ...
      }
    
    }
    

    ContractService.ts:

    export class ContractService extends RestClientService<Contract> {
      private GET_ALL_ITEMS_REST_URI_PATH = "search";
      private GET_ALL_ITEMS_PROPERTY_PATH = "contract";
      public getAllItems(): Observable<Contract[]> {
        return this.GetAll(this.GET_ALL_ITEMS_REST_URI_PATH, this.GET_ALL_ITEMS_PROPERTY_PATH);
      }
    
    }
    

    ContractComponent.ts:

    export class ContractComponent implements OnInit {
    
      getAllItems() {
        this.rcService.getAllItems().subscribe((data) => {
          this.items = data;
       });
      }
    
    }
    
    0 讨论(0)
  • 2020-11-21 06:30

    I found a way to store the http get result into sessionStorage and use it for the session, so that it will never call the server again.

    I used it to call github API to avoid usage limit.

    @Injectable()
    export class HttpCache {
      constructor(private http: Http) {}
    
      get(url: string): Observable<any> {
        let cached: any;
        if (cached === sessionStorage.getItem(url)) {
          return Observable.of(JSON.parse(cached));
        } else {
          return this.http.get(url)
            .map(resp => {
              sessionStorage.setItem(url, resp.text());
              return resp.json();
            });
        }
      }
    }
    

    FYI, sessionStorage limit is 5M(or 4.75M). So, it should not be used like this for large set of data.

    ------ edit -------------
    If you want to have refreshed data with F5, which usesmemory data instead of sessionStorage;

    @Injectable()
    export class HttpCache {
      cached: any = {};  // this will store data
      constructor(private http: Http) {}
    
      get(url: string): Observable<any> {
        if (this.cached[url]) {
          return Observable.of(this.cached[url]));
        } else {
          return this.http.get(url)
            .map(resp => {
              this.cached[url] = resp.text();
              return resp.json();
            });
        }
      }
    }
    
    0 讨论(0)
提交回复
热议问题