angular - RxJs 5 में कोणीय Http नेटवर्क कॉल के परिणाम को साझा करने का सही तरीका क्या है?




angular2-services rxjs5 (15)

Http का उपयोग करके, हम एक विधि को कहते हैं जो एक नेटवर्क कॉल करता है और एक http देखने योग्य देता है:

getCustomer() {
    return this.http.get('/someUrl').map(res => res.json());
}

यदि हम इसे देखने योग्य मानते हैं और इसमें कई ग्राहक जोड़ते हैं:

let network$ = getCustomer();

let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);

हम जो करना चाहते हैं, वह यह सुनिश्चित करता है कि इससे कई नेटवर्क अनुरोध न हों।

यह एक असामान्य परिदृश्य की तरह लग सकता है, लेकिन इसका वास्तव में काफी सामान्य है: उदाहरण के लिए यदि कॉलर त्रुटि संदेश प्रदर्शित करने के लिए अवलोकन करने के लिए सदस्यता लेता है, और इसे async पाइप का उपयोग करके टेम्पलेट को पास करता है, तो हमारे पास पहले से ही दो ग्राहक हैं।

RxJs 5 में ऐसा करने का सही तरीका क्या है?

अर्थात्, यह ठीक काम करने लगता है:

getCustomer() {
    return this.http.get('/someUrl').map(res => res.json()).share();
}

लेकिन क्या यह RxJs 5 में ऐसा करने का मुहावरेदार तरीका है, या हमें इसके बजाय कुछ और करना चाहिए?

नोट: कोणीय 5 नए HttpClient , सभी उदाहरणों में .map(res => res.json()) भाग अब बेकार है, क्योंकि JSON परिणाम अब डिफ़ॉल्ट रूप से मान लिया गया है।


हम जो करना चाहते हैं, वह यह सुनिश्चित करता है कि इससे कई नेटवर्क अनुरोध न हों।

मेरा निजी पसंदीदा नेटवर्क अनुरोध करने वाले कॉल्स के लिए async विधियों का उपयोग करना है। वे विधियाँ स्वयं एक मान नहीं लौटाती हैं, इसके बजाय वे एक ही सेवा के भीतर एक BehaviorSubject अपडेट करते हैं, कौन से घटक सदस्यता लेंगे।

अब एक Observable बजाय एक BehaviorSubject उपयोग क्यों करें? चूंकि,

  • सदस्यता पर BehaviorSubject अंतिम मान लौटाता है जबकि एक नियमित रूप से अवलोकन केवल तभी चालू होता है जब यह एक onnext प्राप्त करता है।
  • यदि आप व्यवहार-रहित कोड के अंतिम मान को गैर-अवलोकन योग्य कोड (सदस्यता के बिना) में प्राप्त करना चाहते हैं, तो आप getValue() विधि का उपयोग कर सकते हैं।

उदाहरण:

customer.service.ts

public customers$: BehaviorSubject<Customer[]> = new BehaviorSubject([]);

public async getCustomers(): Promise<void> {
    let customers = await this.httpClient.post<LogEntry[]>(this.endPoint, criteria).toPromise();
    if (customers) 
        this.customers$.next(customers);
}

फिर, जहाँ कहीं भी आवश्यकता हो, हम केवल ग्राहकों को customers$ सदस्यता दे सकते हैं।

public ngOnInit(): void {
    this.customerService.customers$
    .subscribe((customers: Customer[]) => this.customerList = customers);
}

या हो सकता है कि आप इसे सीधे एक टेम्पलेट में उपयोग करना चाहते हैं

<li *ngFor="let customer of customerService.customers$ | async"> ... </li>

अब, जब तक आप getCustomers लिए एक और कॉल नहीं करते हैं, तब तक customers$ BehaviorSubject में डेटा को बरकरार रखा जाता है।

तो क्या होगा अगर आप इस डेटा को रीफ्रेश करना चाहते हैं? बस getCustomers() लिए एक कॉल करें

public async refresh(): Promise<void> {
    try {
      await this.customerService.getCustomers();
    } 
    catch (e) {
      // request failed, handle exception
      console.error(e);
    }
}

इस पद्धति का उपयोग करते हुए, हमें BehaviorSubject नेटवर्क कॉल के बीच डेटा को स्पष्ट रूप से बनाए रखने की आवश्यकता नहीं है क्योंकि यह BehaviorSubject द्वारा नियंत्रित किया जाता है।

पुनश्च: आमतौर पर जब एक घटक नष्ट हो जाता है तो सदस्यता से छुटकारा पाने के लिए यह एक अच्छा अभ्यास है, इसके लिए आप this उत्तर में सुझाई गई विधि का उपयोग कर सकते हैं।


आप बस ngx-cacheable का उपयोग कर सकते हैं ! यह आपके परिदृश्य के अनुकूल है।

इसका उपयोग करने का लाभ

  • यह केवल एक बार बाकी एपीआई कॉल करता है, प्रतिक्रिया को कैश करता है और निम्नलिखित अनुरोधों के लिए समान देता है।
  • बनाने / अद्यतन / हटाने के ऑपरेशन के बाद आवश्यकतानुसार एपीआई कॉल कर सकते हैं।

तो, आपका सेवा वर्ग कुछ इस तरह होगा -

import { Injectable } from '@angular/core';
import { Cacheable, CacheBuster } from 'ngx-cacheable';

const customerNotifier = new Subject();

@Injectable()
export class customersService {

    // relieves all its caches when any new value is emitted in the stream using notifier
    @Cacheable({
        cacheBusterObserver: customerNotifier,
        async: true
    })
    getCustomer() {
        return this.http.get('/someUrl').map(res => res.json());
    }

    // notifies the observer to refresh the data
    @CacheBuster({
        cacheBusterNotifier: customerNotifier
    })
    addCustomer() {
        // some code
    }

    // notifies the observer to refresh the data
    @CacheBuster({
        cacheBusterNotifier: customerNotifier
    })
    updateCustomer() {
        // some code
    }
}

Here अधिक संदर्भ के लिए लिंक है।


क्या आपके पास पहले से मौजूद कोड चलाने की कोशिश की गई है?

क्योंकि आप जिस वादे का पालन कर रहे हैं, उसमें से ऑब्जर्वेबल का निर्माण कर रहे हैं getJSON() , किसी के भी ग्राहक बनने से पहले नेटवर्क का अनुरोध किया जाता है। और परिणामस्वरूप वादा सभी ग्राहकों द्वारा साझा किया जाता है।

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
// ...

बस इस कैश लेयर का उपयोग करें, यह वह सब कुछ करता है जिसकी आपको आवश्यकता होती है, और यहां तक ​​कि ajax अनुरोधों के लिए कैश का प्रबंधन भी करता है।

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

यह उपयोग करने के लिए बहुत आसान है

@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"}]};
}

परत (एक इंजेक्शन-सक्षम कोणीय सेवा के रूप में) है

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]);
                     }
                     }
                 );
             }
            });
        }
}

यह .publishReplay(1).refCount(); या .publishLast().refCount(); जब से अनुरोध के बाद कोणीय Http वेधशालाएं पूरी हुई हैं।

यह सरल वर्ग परिणाम को कैश करता है ताकि आप कई बार .value की सदस्यता ले सकें और केवल 1 अनुरोध कर सकें। नया अनुरोध करने और डेटा प्रकाशित करने के लिए आप .reload () का भी उपयोग कर सकते हैं।

आप इसका उपयोग कर सकते हैं जैसे:

let res = new RestResource(() => this.http.get('inline.bundleo.js'));

res.status.subscribe((loading)=>{
    console.log('STATUS=',loading);
});

res.value.subscribe((value) => {
  console.log('VALUE=', value);
});

और स्रोत:

export class RestResource {

  static readonly LOADING: string = 'RestResource_Loading';
  static readonly ERROR: string = 'RestResource_Error';
  static readonly IDLE: string = 'RestResource_Idle';

  public value: Observable<any>;
  public status: Observable<string>;
  private loadStatus: Observer<any>;

  private reloader: Observable<any>;
  private reloadTrigger: Observer<any>;

  constructor(requestObservableFn: () => Observable<any>) {
    this.status = Observable.create((o) => {
      this.loadStatus = o;
    });

    this.reloader = Observable.create((o: Observer<any>) => {
      this.reloadTrigger = o;
    });

    this.value = this.reloader.startWith(null).switchMap(() => {
      if (this.loadStatus) {
        this.loadStatus.next(RestResource.LOADING);
      }
      return requestObservableFn()
        .map((res) => {
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.IDLE);
          }
          return res;
        }).catch((err)=>{
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.ERROR);
          }
          return Observable.of(null);
        });
    }).publishReplay(1).refCount();
  }

  reload() {
    this.reloadTrigger.next(null);
  }

}


Rxjs ऑब्जर्वर / ऑब्जर्वेबल + कैशिंग + सब्सक्रिप्शन का उपयोग करके कैशेबल HTTP रिस्पॉन्स डेटा

नीचे कोड देखें

* अस्वीकरण: मैं rxjs के लिए नया हूं, इसलिए ध्यान रखें कि मैं अवलोकन योग्य / पर्यवेक्षक दृष्टिकोण का दुरुपयोग कर सकता हूं। मेरा समाधान विशुद्ध रूप से अन्य समाधानों का एक समूह है जो मैंने पाया, और एक सरल अच्छी तरह से दस्तावेजी समाधान खोजने में विफल होने का परिणाम है। इस प्रकार मैं अपना पूरा कोड समाधान प्रदान कर रहा हूं (जैसा कि मैंने पाया होगा) उम्मीद में कि यह दूसरों की मदद करता है।

* ध्यान दें, यह दृष्टिकोण GoogleFirebaseObservables पर आधारित है। दुर्भाग्य से मेरे पास हुड के नीचे जो कुछ भी था उसे दोहराने के लिए उचित अनुभव / समय का अभाव है। लेकिन कुछ कैश-सक्षम डेटा तक अतुल्यकालिक पहुंच प्रदान करने का एक सरल तरीका निम्नलिखित है।

स्थिति : एक 'उत्पाद-सूची' घटक को उत्पादों की सूची प्रदर्शित करने का काम सौंपा गया है। साइट कुछ मेनू बटन के साथ एक सिंगल-पेज वेब ऐप है जो पेज पर प्रदर्शित उत्पादों को 'फ़िल्टर' करेगा।

समाधान : घटक एक सेवा पद्धति के लिए "सबस्क्राइब" करता है। सेवा पद्धति उत्पाद वस्तुओं की एक सरणी देती है, जो घटक सदस्यता कॉलबैक के माध्यम से पहुंचता है। सेवा पद्धति एक नए बनाए गए ऑब्जर्वर में अपनी गतिविधि लपेटती है और पर्यवेक्षक को लौटा देती है। इस प्रेक्षक के अंदर, यह कैश्ड डेटा खोजता है और इसे सब्सक्राइबर (कंपोनेंट) को वापस भेजता है और रिटर्न देता है। अन्यथा यह डेटा को पुनः प्राप्त करने के लिए http कॉल जारी करता है, प्रतिक्रिया के लिए सब्सक्राइब करता है, जहाँ आप उस डेटा को संसाधित कर सकते हैं (उदाहरण के लिए डेटा को अपने मॉडल में मैप करें) और फिर ग्राहक को डेटा वापस दें।

कोड

उत्पाद-list.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { ProductService } from '../../../services/product.service';
import { Product, ProductResponse } from '../../../models/Product';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products: Product[];

  constructor(
    private productService: ProductService
  ) { }

  ngOnInit() {
    console.log('product-list init...');
    this.productService.getProducts().subscribe(products => {
      console.log('product-list received updated products');
      this.products = products;
    });
  }
}

product.service.ts

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import { Observable, Observer } from 'rxjs';
import 'rxjs/add/operator/map';
import { Product, ProductResponse } from '../models/Product';

@Injectable()
export class ProductService {
  products: Product[];

  constructor(
    private http:Http
  ) {
    console.log('product service init.  calling http to get products...');

  }

  getProducts():Observable<Product[]>{
    //wrap getProducts around an Observable to make it async.
    let productsObservable$ = Observable.create((observer: Observer<Product[]>) => {
      //return products if it was previously fetched
      if(this.products){
        console.log('## returning existing products');
        observer.next(this.products);
        return observer.complete();

      }
      //Fetch products from REST API
      console.log('** products do not yet exist; fetching from rest api...');
      let headers = new Headers();
      this.http.get('http://localhost:3000/products/',  {headers: headers})
      .map(res => res.json()).subscribe((response:ProductResponse) => {
        console.log('productResponse: ', response);
        let productlist = Product.fromJsonList(response.products); //convert service observable to product[]
        this.products = productlist;
        observer.next(productlist);
      });
    }); 
    return productsObservable$;
  }
}

product.ts (मॉडल)

export interface ProductResponse {
  success: boolean;
  msg: string;
  products: Product[];
}

export class Product {
  product_id: number;
  sku: string;
  product_title: string;
  ..etc...

  constructor(product_id: number,
    sku: string,
    product_title: string,
    ...etc...
  ){
    //typescript will not autoassign the formal parameters to related properties for exported classes.
    this.product_id = product_id;
    this.sku = sku;
    this.product_title = product_title;
    ...etc...
  }



  //Class method to convert products within http response to pure array of Product objects.
  //Caller: product.service:getProducts()
  static fromJsonList(products:any): Product[] {
    let mappedArray = products.map(Product.fromJson);
    return mappedArray;
  }

  //add more parameters depending on your database entries and constructor
  static fromJson({ 
      product_id,
      sku,
      product_title,
      ...etc...
  }): Product {
    return new Product(
      product_id,
      sku,
      product_title,
      ...etc...
    );
  }
}

जब मैं Chrome में पृष्ठ लोड करता हूं, तो मेरे द्वारा देखे जाने वाले आउटपुट का एक नमूना यहां होता है। ध्यान दें कि प्रारंभिक भार पर, उत्पादों को http (मेरे नोड बाकी सेवा पर कॉल करें), जो स्थानीय स्तर पर पोर्ट 3000 पर चल रहा है) से लिया जाता है। जब मैं उत्पादों के 'फ़िल्टर किए गए' दृश्य पर नेविगेट करने के लिए क्लिक करता हूं, तो उत्पाद कैश में पाए जाते हैं।

मेरा Chrome लॉग (कंसोल):

core.es5.js:2925 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
app.component.ts:19 app.component url: /products
product.service.ts:15 product service init.  calling http to get products...
product-list.component.ts:18 product-list init...
product.service.ts:29 ** products do not yet exist; fetching from rest api...
product.service.ts:33 productResponse:  {success: true, msg: "Products found", products: Array(23)}
product-list.component.ts:20 product-list received updated products

... [उत्पादों को फ़िल्टर करने के लिए एक मेनू बटन पर क्लिक किया] ...

app.component.ts:19 app.component url: /products/chocolatechip
product-list.component.ts:18 product-list init...
product.service.ts:24 ## returning existing products
product-list.component.ts:20 product-list received updated products

निष्कर्ष: यह सबसे आसान तरीका है जो मैंने (अभी तक) कैचवेबल http प्रतिक्रिया डेटा को लागू करने के लिए पाया है। मेरे कोणीय ऐप में, हर बार जब मैं उत्पादों के एक अलग दृश्य पर नेविगेट करता हूं, तो उत्पाद-सूची घटक पुनः लोड होता है। ProductService एक साझा उदाहरण प्रतीत होता है, इसलिए ProductService में 'उत्पादों: उत्पाद [] के स्थानीय कैश को नेविगेशन के दौरान बरकरार रखा जाता है, और बाद में "GetProducts ()" कॉल किए गए मान को वापस कर देता है। एक अंतिम टिप्पणी, मैंने टिप्पणियों के बारे में पढ़ा है कि जब आप 'मेमोरी लीक' को रोकने के लिए पर्यवेक्षक / सदस्यता को बंद करने की आवश्यकता होती है। मैंने इसे यहाँ शामिल नहीं किया है, लेकिन इसे ध्यान में रखना कुछ है।


अद्यतन करें: बेन लेश का कहना है कि अगली मामूली रिहाई 5.2.0 के बाद, आप शेयररेप्ले () को सही मायने में कैश करने के लिए कॉल कर पाएंगे।

पहले से .....

सबसे पहले, शेयर () या publishReplay (1) .refCount () का उपयोग न करें, वे एक ही हैं और इसके साथ समस्या यह है कि यह केवल तभी शेयर करता है जब कनेक्शन सक्रिय होते समय किए जाते हैं, यदि आप इसे पूरा करने के बाद कनेक्ट करते हैं , यह फिर से एक नया अवलोकन बनाता है, अनुवाद, वास्तव में कैशिंग नहीं।

Birowski ने ऊपर सही समाधान दिया, जो कि ReplaySubject का उपयोग करना है। ReplaySubject उन मानों को कैश कर देगा जो आप हमारे मामले में देते हैं (बफरसाइज़) 1. यह एक नए ऑब्जर्वेबल को शेयर की तरह नहीं बनाएगा () एक बार RefCount शून्य तक पहुँच जाता है और आप एक नया कनेक्शन बनाते हैं, जो कैशिंग के लिए सही व्यवहार है।

यहाँ एक पुन: प्रयोज्य कार्य है

export function cacheable<T>(o: Observable<T>): Observable<T> {
  let replay = new ReplaySubject<T>(1);
  o.subscribe(
    x => replay.next(x),
    x => replay.error(x),
    () => replay.complete()
  );
  return replay.asObservable();
}

यहाँ इसका उपयोग कैसे करना है

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { cacheable } from '../utils/rxjs-functions';

@Injectable()
export class SettingsService {
  _cache: Observable<any>;
  constructor(private _http: Http, ) { }

  refresh = () => {
    if (this._cache) {
      return this._cache;
    }
    return this._cache = cacheable<any>(this._http.get('YOUR URL'));
  }
}

नीचे कैचएबल फ़ंक्शन का एक और अग्रिम संस्करण है यह एक अपनी लुकअप तालिका + एक कस्टम लुकअप तालिका प्रदान करने की क्षमता है। इस तरह, आपको ऊपर दिए उदाहरण में यह चेक नहीं करना चाहिए। यह भी ध्यान रखें कि अवलोकन को पहले तर्क के रूप में पारित करने के बजाय, आप एक फ़ंक्शन पास करते हैं जो वेधशालाओं को लौटाता है, इसका कारण यह है कि कोणीय का Http तुरंत निष्पादित होता है, इसलिए एक आलसी निष्पादित फ़ंक्शन को वापस करके, हम कॉल नहीं करने का निर्णय ले सकते हैं यदि यह पहले से ही है। हमारे कैश।

let cacheableCache: { [key: string]: Observable<any> } = {};
export function cacheable<T>(returnObservable: () => Observable<T>, key?: string, customCache?: { [key: string]: Observable<T> }): Observable<T> {
  if (!!key && (customCache || cacheableCache)[key]) {
    return (customCache || cacheableCache)[key] as Observable<T>;
  }
  let replay = new ReplaySubject<T>(1);
  returnObservable().subscribe(
    x => replay.next(x),
    x => replay.error(x),
    () => replay.complete()
  );
  let observable = replay.asObservable();
  if (!!key) {
    if (!!customCache) {
      customCache[key] = observable;
    } else {
      cacheableCache[key] = observable;
    }
  }
  return observable;
}

उपयोग:

getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")

इस article अनुसार

यह पता चला है कि हम आसानी से पब्लिशेबल को पब्लिश कर सकते हैं पब्लिशरप्ले (1) और रिफेकाउंट जोड़कर।

तो अंदर अगर बयान सिर्फ append

.publishReplay(1)
.refCount();

.map(...)


डेटा कैश करें और यदि कैश उपलब्ध है, तो इसे वापस करें अन्यथा HTTP अनुरोध करें।

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;
    }
  }
}

प्लंकर उदाहरण

यह आर्टिलरी https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html एक शानदार विवरण है कि कैसे shareReplay साथ कैश किया shareReplay


मैंने http प्राप्त करने के लिए सेशनस्टोरेज में स्टोर करने और सत्र के लिए उपयोग करने का एक तरीका पाया, ताकि यह फिर से सर्वर को कभी भी कॉल न करे।

मैंने इसका उपयोग उपयोग सीमा से बचने के लिए github API को कॉल करने के लिए किया था।

@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 सीमा 5M (या 4.75M) है। तो, यह डेटा के बड़े सेट के लिए इस तरह का उपयोग नहीं किया जाना चाहिए।

------ संपादित करें -------------
यदि आप F5 के साथ ताज़ा डेटा चाहते हैं, जो सेशनस्टोरेज के बजाय डेटा का उपयोग करता है;

@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();
        });
    }
  }
}

मैंने प्रश्न को तारांकित किया है, लेकिन मैं कोशिश करूँगा और इस पर जाऊंगा।

//this will be the shared observable that 
//anyone can subscribe to, get the value, 
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);

getCustomer().subscribe(customer$);

//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));

//here's the second subscriber
setTimeout(() => {
  customer$.subscribe(val => console.log('subscriber 2: ' + val));  
}, 1000);

function getCustomer() {
  return new Rx.Observable(observer => {
    console.log('api request');
    setTimeout(() => {
      console.log('api response');
      observer.next('customer object');
      observer.complete();
    }, 500);
  });
}

यहाँ proof :)

वहाँ है, लेकिन एक takeaway: getCustomer().subscribe(customer$)

हम getCustomer() की एपीआई प्रतिक्रिया की सदस्यता नहीं ले रहे हैं, हम एक ReplaySubject की सदस्यता ले रहे हैं जो अवलोकन योग्य है जो एक अलग ऑब्जर्वेबल की सदस्यता लेने में भी सक्षम है (और यह महत्वपूर्ण है) यह अंतिम रूप से निर्धारित मूल्य है और इसे किसी भी पर पुनर्प्रकाशित करना यह (ReplaySubject के) ग्राहक हैं।


rxjs 5.4.0 में एक नया शेयर रीप्ले विधि है।

लेखक स्पष्ट रूप से कहते हैं "AJAX परिणाम कैशिंग की तरह चीजों को संभालने के लिए आदर्श"

rxjs PR # 2443 करतब (shareReplay): shareReplay का shareReplay संस्करण जोड़ता है

shareReplay एक अवलोकन योग्य रिटर्न देता है जो एक ReplaySubject पर बहुरंगी स्रोत है। उस पुनरावृत्ति विषय को स्रोत से त्रुटि पर पुनर्नवीनीकरण किया जाता है, लेकिन स्रोत के पूरा होने पर नहीं। यह AJAX परिणामों को कैच करने जैसी चीजों से निपटने के लिए shareReplay को आदर्श बनाता है, क्योंकि यह पुन: प्रयोज्य है। हालांकि, यह दोहराव वाला व्यवहार है, लेकिन यह इस बात से अलग है कि यह स्रोत को देखने योग्य नहीं दोहराएगा, बल्कि यह स्रोत के मानों को दोहराएगा।


rxjs संस्करण 5.4.0 (2017-05-09) shareReplay लिए समर्थन जोड़ता है।

ShareReplay का उपयोग क्यों करें?

जब आप साइड-इफेक्ट्स या टैक्सिंग कंप्युटेशन करते हैं, तो आप आमतौर पर शेयर-रीप्ले का उपयोग करना चाहते हैं, जिसे आप कई सब्सक्राइबरों द्वारा निष्पादित नहीं करना चाहते हैं। यह उन स्थितियों में भी मूल्यवान हो सकता है जहां आप जानते हैं कि आपके पास एक देर से सब्सक्राइबर होंगे जो पहले उत्सर्जित मूल्यों तक पहुंच की आवश्यकता होगी। सदस्यता पर मूल्यों को फिर से खेलना करने की यह क्षमता शेयर और शेयरप्ले को अलग करती है।

आप इसका उपयोग करने के लिए एक कोणीय सेवा को आसानी से संशोधित कर सकते हैं और एक कैश्ड परिणाम के साथ एक अवलोकनीय लौटा सकते हैं जो केवल http कॉल को एक बार बना देगा (यह मानते हुए कि पहली कॉल सफल थी)।

उदाहरण कोणीय सेवा

यहाँ एक बहुत ही सरल ग्राहक सेवा है जो shareReplay का उपयोग करता है।

customer.service.ts

  @CacheObservableArgsKey
  getMyThing(id: string): Observable<any> {
    return this.http.get('things/'+id);
  }

ध्यान दें कि कंस्ट्रक्टर में असाइनमेंट को विधि getCustomers ले जाया जा सकता है, लेकिन जैसा कि HttpClient से लौटे getCustomers "ठंडा" हैं, यह कंस्ट्रक्टर में करना स्वीकार्य है क्योंकि http कॉल केवल हर कॉल करने के लिए पहली कॉल के साथ की जाएगी।

साथ ही यहाँ धारणा यह है कि प्रारंभिक लौटा डेटा जीवन भर के लिए नहीं मिलता है।


नक्शे के बाद और किसी भी सदस्यता से पहले सिर्फ कॉल शेयर () करें ।

मेरे मामले में, मेरे पास एक सामान्य सेवा (RestClientService.ts) है, जो बाकी कॉल कर रही है, डेटा निकाल रही है, त्रुटियों की जांच कर रही है और एक ठोस कार्यान्वयन सेवा के लिए अवलोकन योग्य है (f.ex .: ContractClientService.ts), आखिरकार यह ठोस कार्यान्वयन कॉन्ट्रैक्टकम्पोनेंट। टीट्स के लिए अवलोकन योग्य है, और यह दृश्य को अपडेट करने के लिए सदस्यता लेता है।

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;
   });
  }

}






rxjs5