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


303

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 परिणाम अब डिफ़ॉल्ट रूप से माना जाता है।


1
> शेयर प्रकाशित करने के लिए समान है ()। RefCount ()। वास्तव में यह नहीं है। निम्नलिखित चर्चा देखें: github.com/ReactiveX/rxjs/issues/1363
क्रिश्चियन

1
संपादित प्रश्न, समस्या के अनुसार कोड पर डॉक्स अपडेट किए जाने की आवश्यकता है -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
कोणीय विश्वविद्यालय

मुझे लगता है कि 'यह निर्भर करता है'। लेकिन उन कॉल के लिए जहां आप स्थानीय रूप से डेटा को कैश नहीं कर सकते b / c यह पैरामीटर / कॉम्बिनेशन बदलने के कारण समझ में नहीं आता है। share () बिल्कुल सही बात लगती है। लेकिन अगर आप चीजों को स्थानीय रूप से कैश कर सकते हैं तो ReplaySubject / BehaviorSubject के बारे में कुछ अन्य उत्तर भी अच्छे समाधान हैं।
जिमबी

मुझे लगता है कि न केवल हमें डेटा को कैश करने की आवश्यकता है, हमें कैश किए गए डेटा को अपडेट / संशोधित करने की भी आवश्यकता है। यह एक सामान्य मामला है। उदाहरण के लिए, यदि मैं कैश्ड मॉडल के लिए एक नया क्षेत्र जोड़ना चाहता हूं या क्षेत्र के मूल्य को अपडेट करना चाहता हूं। हो सकता है कि बनाने के एक सिंगलटन DataCacheService साथ CRUD विधि एक बेहतर तरीका है? Redux के स्टोर की तरह । तुम क्या सोचते हो?
स्लाइड शो 2

आप बस ngx-cacheable का उपयोग कर सकते हैं ! यह आपके परिदृश्य के अनुकूल है। नीचे मेरा जवाब देखें
तुषार वालज़ादे

जवाबों:


230

डेटा कैश करें और यदि कैश उपलब्ध है, तो इसे वापस करें अन्यथा 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


3
do()इसके विपरीत map()घटना को संशोधित करने के लिए नहीं। आप भी उपयोग कर सकते हैं map()लेकिन फिर आपको कॉलबैक के अंत में सही मान वापस करना होगा।
गुंटर ज़ोचबॉयर

3
यदि कॉल-साइट जो .subscribe()आपको उस मूल्य की आवश्यकता नहीं है, जो आप कर सकते हैं, क्योंकि यह सिर्फ null( this.extractDataरिटर्न के आधार पर ) मिल सकता है , लेकिन IMHO यह कोड के इरादे को अच्छी तरह से व्यक्त नहीं करता है।
गुंटर ज़ोचबॉयर

2
जब this.extraDataसमाप्त होता है extraData() { if(foo) { doSomething();}}अन्यथा अन्यथा अंतिम अभिव्यक्ति का परिणाम वापस आ जाता है जो कि आप जो चाहते हैं वह नहीं हो सकता है।
गुंटर ज़ोचबॉयर

9
@ गुंटर, कोड के लिए धन्यवाद, यह काम करता है। हालांकि, मैं यह समझने की कोशिश कर रहा हूं कि आप डेटा और ऑब्जर्वेबल का ट्रैक अलग-अलग क्यों रख रहे हैं। क्या आप इस तरह से केवल प्रेक्षित <डेटा> को कैशिंग करके समान रूप से प्रभाव प्राप्त नहीं करेंगे? if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
जुलाई .टेक

3
@HarleenKaur यह एक ऐसा वर्ग है जिसे प्राप्त JSON मजबूत प्रकार की जाँच और स्वतः पूर्णता प्राप्त करने के लिए प्रेरित है। इसका उपयोग करने की कोई आवश्यकता नहीं है, लेकिन यह आम है।
गुंटर ज़ोचबॉयर

44

@Cristian सुझाव के अनुसार, यह एक ऐसा तरीका है जो HTTP वेधशालाओं के लिए अच्छा काम करता है, जो केवल एक बार ही निकलते हैं और फिर वे पूर्ण होते हैं:

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

इस दृष्टिकोण का उपयोग करने के साथ कुछ समस्याएं हैं - अवलोकन योग्य लौटे हुए को रद्द या वापस नहीं लिया जा सकता है। यह आपके लिए एक मुद्दा नहीं हो सकता है, लेकिन फिर से यह हो सकता है। यदि यह एक समस्या है, तो shareऑपरेटर एक उचित विकल्प हो सकता है (भले ही कुछ बुरा मामलों के साथ)। विकल्पों पर एक गहरी गोता चर्चा के लिए इस ब्लॉग पोस्ट में टिप्पणी अनुभाग देखें: blog.jhades.org/…
क्रिश्चियन

1
छोटा स्पष्टीकरण ... हालाँकि कड़ाई से पालन किए जा रहे स्रोत publishLast().refCount()को रद्द नहीं किया जा सकता है, लेकिन एक बार अवलोकन द्वारा वापस लिए गए सभी सदस्यता refCountरद्द कर दिए जाने पर, शुद्ध प्रभाव स्रोत का अवलोकन योग्य नहीं रह जाएगा, इसे रद्द कर दिया जाएगा यदि यह "इनफ़्लाइट"
ईसाई

@ क्रिसियन अरे, क्या आप यह समझा सकते हैं कि "रद्द नहीं किया जा सकता है या नहीं कहा जा सकता है"? धन्यवाद।
अपरिभाषित

37

अद्यतन करें: बेन लेश का कहना है कि अगली मामूली रिलीज 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")

क्या इस समाधान को RxJs ऑपरेटर के रूप में उपयोग नहीं करने का कोई कारण है const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();:? इसलिए यह किसी भी अन्य ऑपरेटर की तरह व्यवहार करता है ..
फेलिक्स

31

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

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

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

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


क्या यह इससे संबंधित है? हालांकि ये डॉक्स 2014 से हैं। github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
हारून हॉफमैन

4
मैंने एक अवलोकन के लिए .shareReplay (1, 10000) को जोड़ने की कोशिश की, लेकिन मैंने किसी भी कैशिंग या व्यवहार परिवर्तन पर ध्यान नहीं दिया। क्या कोई कार्यशील उदाहरण उपलब्ध है?
एडीस-मैथ्यू

चेंजगॉग github.com/ReactiveX/rxjs/blob/… को देखते हुए, यह पहले दिखाई दिया था, v5 में हटा दिया गया था, 5.4 में वापस जोड़ा गया - आरएक्स-बुक लिंक v4 को संदर्भित करता है, लेकिन यह वर्तमान LTS v5.5.6 में मौजूद है और यह v6 में है। मुझे लगता है कि आरएक्स-बुक लिंक तारीख से बाहर है।
जेसन अब्बी

25

इस लेख के अनुसार

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

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

.publishReplay(1)
.refCount();

सेवा .map(...)


11

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

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

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

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

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

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

customer.service.ts

import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class CustomerService {

    private readonly _getCustomers: Observable<ICustomer[]>;

    constructor(private readonly http: HttpClient) {
        this._getCustomers = this.http.get<ICustomer[]>('/api/customers/').pipe(shareReplay());
    }

    getCustomers() : Observable<ICustomer[]> {
        return this._getCustomers;
    }
}

export interface ICustomer {
  /* ICustomer interface fields defined here */
}

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

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


मुझे वास्तव में यह पैटर्न पसंद है और मैं इसे एपली सेवाओं के एक साझा पुस्तकालय के भीतर लागू करना चाहता हूं जिसका उपयोग मैं कई अनुप्रयोगों में करता हूं। एक उदाहरण एक उपयोगकर्ता सेवा है, और हर जगह एक जोड़े को छोड़कर ऐप के जीवनकाल के दौरान कैश को अमान्य करने की आवश्यकता नहीं है, लेकिन उन मामलों के लिए, मैं पिछली सदस्यता को अनाथ किए बिना इसे अमान्य करने के बारे में कैसे जाऊंगा?
सिरोपथम हाट

10

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

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

यहाँ सबूत है :)

लेकिन एक टेकअवे है: getCustomer().subscribe(customer$)

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


1
मुझे यह दृष्टिकोण पसंद है क्योंकि यह rxjs का अच्छा उपयोग करता है और कस्टम तर्क जोड़ने की कोई आवश्यकता नहीं है, धन्यवाद
थिबस

7

मैंने 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 करें, सेशनस्टोरेज लिमिट 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();
        });
    }
  }
}

यदि आप सेशन स्टोरेज में स्टोर करेंगे तो आप यह कैसे सुनिश्चित करेंगे कि ऐप छोड़ने पर सेशन स्टोरेज नष्ट हो जाए?
गग्स

लेकिन यह उपयोगकर्ता के लिए अप्रत्याशित व्यवहार का परिचय देता है। जब उपयोगकर्ता ब्राउज़र के F5 या ताज़ा बटन को हिट करता है, तो वह सर्वर से ताज़ा डेटा की उम्मीद करता है। लेकिन वास्तव में उसे लोकलस्टोरेज से पुराना डेटा मिल रहा है। आने वाली बग रिपोर्ट, सपोर्ट टिकट इत्यादि। जैसा कि नाम में sessionStorageकहा गया है, मैं इसका इस्तेमाल केवल उन्हीं डेटा के लिए करूंगा, जो पूरे सत्र के अनुरूप होने की उम्मीद है।
मार्टिन श्नाइडर

@ MA-Maddin जैसा कि मैंने कहा "मैंने इसका उपयोग उपयोग की सीमा से बचने के लिए किया था"। यदि आप चाहते हैं कि डेटा F5 के साथ ताज़ा हो जाए, तो आपको सेशनस्टोरेज के बजाय मेमोरी का उपयोग करने की आवश्यकता है। इस दृष्टिकोण के साथ उत्तर को संपादित किया गया है।
एलनह्विक

हाँ, यह एक उपयोग मामला हो सकता है। मैं बस तभी से ट्रिगर हो गया जब हर कोई कैश के बारे में बात कर रहा है और ओपी getCustomerउसके उदाहरण में है। ;) तो बस कुछ पीपीएल को चेतावनी देना चाहता था जो जोखिम नहीं देख सकते हैं :)
मार्टिन श्नाइडर

5

आपके द्वारा चुना गया कार्यान्वयन इस बात पर निर्भर करेगा कि आप अपने HTTP अनुरोध को रद्द करना चाहते हैं या नहीं।

किसी भी मामले में, टाइपस्क्रिप्ट डेकोरेटर व्यवहार को मानकीकृत करने का एक अच्छा तरीका है। यह वही है जो मैंने लिखा था:

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

डेकोरेटर परिभाषा:

/**
 * Decorator that replays and connects to the Observable returned from the function.
 * Caches the result using all arguments to form a key.
 * @param target
 * @param name
 * @param descriptor
 * @returns {PropertyDescriptor}
 */
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
  const originalFunc = descriptor.value;
  const cacheMap = new Map<string, any>();
  descriptor.value = function(this: any, ...args: any[]): any {
    const key = args.join('::');

    let returnValue = cacheMap.get(key);
    if (returnValue !== undefined) {
      console.log(`${name} cache-hit ${key}`, returnValue);
      return returnValue;
    }

    returnValue = originalFunc.apply(this, args);
    console.log(`${name} cache-miss ${key} new`, returnValue);
    if (returnValue instanceof Observable) {
      returnValue = returnValue.publishReplay(1);
      returnValue.connect();
    }
    else {
      console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
    }
    cacheMap.set(key, returnValue);
    return returnValue;
  };

  return descriptor;
}

हाय @Alo - ऊपर उदाहरण संकलित नहीं करता है। Property 'connect' does not exist on type '{}'.लाइन से returnValue.connect();। क्या आप विस्तार से समझा सकते हैं?
होफ

4

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 ()" कॉल किए गए मूल्य को वापस कर देता है। एक अंतिम नोट, मैंने टिप्पणियों के बारे में पढ़ा है कि जब आप 'मेमोरी लीक' को रोकने के लिए पर्यवेक्षक / सदस्यता को बंद करने की आवश्यकता होती है। मैंने इसे यहाँ शामिल नहीं किया है, लेकिन इसे ध्यान में रखना कुछ है।


1
नोट - मैंने तब से एक अधिक शक्तिशाली समाधान पाया है, जिसमें RxJS BehaviorSubjects शामिल है, जो कोड को सरल करता है और नाटकीय रूप से head ओवरहेड ’पर कटौती करता है। Products.service.ts में, 1. 'rxjs' से {BehaviorSubject} आयात करें; 2. परिवर्तन 'उत्पादों: उत्पाद []' में उत्पाद $: व्यवहार व्यवहार <उत्पाद []> = नया व्यवहार विषय <उत्पाद []> ([]); ' 3. अब आप बस कुछ भी वापस किए बिना http कॉल कर सकते हैं। http_getProducts () {this.http.get (...)। map (res => res.j.j) ()) सदस्यता लें (उत्पादों => this.product $ .next (उत्पादों))};
ObjectiveTC

1
स्थानीय चर 'उत्पाद $' एक व्यवहार-विषय है, जो EMIT और STORE दोनों नवीनतम उत्पादों (उत्पाद $ .next (.. से भाग 3 में कॉल) में होगा। अब अपने घटकों में, सेवा को सामान्य रूप से इंजेक्ट करें। आपको productService.product $ .value का उपयोग करके उत्पाद का सबसे हाल ही में निर्दिष्ट मूल्य $ प्राप्त होता है। या उत्पाद $ की सदस्यता लें यदि आप एक क्रिया करना चाहते हैं जब भी उत्पाद $ को एक नया मूल्य प्राप्त होता है (यानी, उत्पाद $ .next (...) फ़ंक्शन को भाग 3 में कहा जाता है)।
ObjectiveTC

1
उदाहरण के लिए, products.component.ts में ... this.productService.product $ .takeUntil (this.ngUnsubscribe) .subscribe ((उत्पाद) => {this.category); फ़िल्टर किए जाने देंप्रोडक्ट्स = this.productService.getProductsByCategory (यह। श्रेणी); this.products = फ़िल्टर किए गए उत्पाद; });
ObjectiveTC

1
वेधशालाओं से सदस्यता समाप्त करने के बारे में एक महत्वपूर्ण टिप्पणी: ".takeUntil (this.ngUnsubscribe)"। इस स्टैक ओवरफ्लो प्रश्न / उत्तर को देखें, जो 'de-facto' को घटनाओं से अनसब्सक्राइब करने के लिए अनुशंसित तरीके को दिखाता है: stackoverflow.com/questions/38008334/…
ObjectiveTC

1
वैकल्पिक है .first () या .take (1) यदि अवलोकनीय केवल एक बार डेटा प्राप्त करने के लिए है। वेधशालाओं की अन्य सभी 'अनंत धाराएँ' 'ngOnDestroy ()' में सदस्यता समाप्त होनी चाहिए, और यदि आप नहीं करते हैं तो आप डुप्लिकेट 'पर्यवेक्षित' कॉलबैक के साथ समाप्त हो सकते हैं। stackoverflow.com/questions/28007777/…
ObjectiveTC

3

मेरा मानना ​​है कि http कॉल के लिए कैशिंग सुविधाओं को बनाए रखने के लिए @ ngx-cache / core उपयोगी हो सकता है, खासकर अगर HTTP कॉल ब्राउज़र और सर्वर प्लेटफॉर्म दोनों पर की जाती है

मान लें कि हमारे पास निम्नलिखित विधि है:

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

आप HTTP कॉल करने की विधि से लौटे मूल्य को संग्रहीत करने के लिए @ (एनजी-कैश / कोर के Cachedडेकोरेटर का उपयोग कर सकते हैं ( यह विन्यास योग्य हो सकता है, कृपया एनजी-बीज / सार्वभौमिक पर कार्यान्वयन की जांच करें ) - पहले निष्पादन पर ही सही। अगली बार विधि लागू होने पर ( ब्राउज़र या सर्वर प्लेटफ़ॉर्म पर कोई फर्क नहीं पड़ता ), मान को फिर से प्राप्त किया जाता है ।cache storagestoragecache storage

import { Cached } from '@ngx-cache/core';

...

@Cached('get-customer') // the cache key/identifier
getCustomer() {
  return this.http.get('/someUrl').map(res => res.json());
}

वहाँ भी करने के लिए उपयोग कैशिंग विधियों (संभावना है has, get, set) का उपयोग कर कैशिंग एपीआई

anyclass.ts

...
import { CacheService } from '@ngx-cache/core';

@Injectable()
export class AnyClass {
  constructor(private readonly cache: CacheService) {
    // note that CacheService is injected into a private property of AnyClass
  }

  // will retrieve 'some string value'
  getSomeStringValue(): string {
    if (this.cache.has('some-string'))
      return this.cache.get('some-string');

    this.cache.set('some-string', 'some string value');
    return 'some string value';
  }
}

यहां क्लाइंट-साइड और सर्वर-साइड कैशिंग दोनों के लिए पैकेजों की सूची दी गई है:


1

rxjs 5.3.0

मैं खुश नहीं हूं .map(myFunction).publishReplay(1).refCount()

कई ग्राहकों के साथ, कुछ मामलों में दो बार .map()निष्पादित myFunctionहोता है (मुझे उम्मीद है कि यह केवल एक बार निष्पादित होगा)। एक तय लग रहा हैpublishReplay(1).refCount().take(1)

एक और चीज जो आप कर सकते हैं, वह सिर्फ उपयोग नहीं है refCount()और ऑब्जर्वेबल को तुरंत गर्म करें:

let obs = this.http.get('my/data.json').publishReplay(1);
obs.connect();
return obs;

यह सब्सक्राइबरों की परवाह किए बिना HTTP अनुरोध शुरू करेगा। मुझे यकीन नहीं है कि HTTP GET खत्म होने से पहले इसे रद्द कर दिया जाएगा या नहीं।


1

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

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

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

  • सदस्यता पर 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$

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

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

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

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


1

शानदार जवाब।

या आप ऐसा कर सकते हैं:

यह rxjs के नवीनतम संस्करण से है। मैं RxJS के 5.5.7 संस्करण का उपयोग कर रहा हूं

import {share} from "rxjs/operators";

this.http.get('/someUrl').pipe(share());

0

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

मेरे मामले में, मेरे पास एक सामान्य सेवा (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;
   });
  }

}

0

मैंने कैश क्लास लिखा,

/**
 * Caches results returned from given fetcher callback for given key,
 * up to maxItems results, deletes the oldest results when full (FIFO).
 */
export class StaticCache
{
    static cachedData: Map<string, any> = new Map<string, any>();
    static maxItems: number = 400;

    static get(key: string){
        return this.cachedData.get(key);
    }

    static getOrFetch(key: string, fetcher: (string) => any): any {
        let value = this.cachedData.get(key);

        if (value != null){
            console.log("Cache HIT! (fetcher)");
            return value;
        }

        console.log("Cache MISS... (fetcher)");
        value = fetcher(key);
        this.add(key, value);
        return value;
    }

    static add(key, value){
        this.cachedData.set(key, value);
        this.deleteOverflowing();
    }

    static deleteOverflowing(): void {
        if (this.cachedData.size > this.maxItems) {
            this.deleteOldest(this.cachedData.size - this.maxItems);
        }
    }

    /// A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
    /// However that seems not to work. Trying with forEach.
    static deleteOldest(howMany: number): void {
        //console.debug("Deleting oldest " + howMany + " of " + this.cachedData.size);
        let iterKeys = this.cachedData.keys();
        let item: IteratorResult<string>;
        while (howMany-- > 0 && (item = iterKeys.next(), !item.done)){
            //console.debug("    Deleting: " + item.value);
            this.cachedData.delete(item.value); // Deleting while iterating should be ok in JS.
        }
    }

    static clear(): void {
        this.cachedData = new Map<string, any>();
    }

}

हम इसका उपयोग कैसे करते हैं यह सब स्थिर है, लेकिन इसे एक सामान्य वर्ग और सेवा बनाने के लिए स्वतंत्र महसूस करें। मुझे यकीन नहीं है कि अगर कोणीय पूरे समय के लिए एक ही उदाहरण रखता है (हालांकि Angular2 के लिए नया)।

और इस तरह से मैं इसका उपयोग करता हूं:

            let httpService: Http = this.http;
            function fetcher(url: string): Observable<any> {
                console.log("    Fetching URL: " + url);
                return httpService.get(url).map((response: Response) => {
                    if (!response) return null;
                    if (typeof response.json() !== "array")
                        throw new Error("Graph REST should return an array of vertices.");
                    let items: any[] = graphService.fromJSONarray(response.json(), httpService);
                    return array ? items : items[0];
                });
            }

            // If data is a link, return a result of a service call.
            if (this.data[verticesLabel][name]["link"] || this.data[verticesLabel][name]["_type"] == "link")
            {
                // Make an HTTP call.
                let url = this.data[verticesLabel][name]["link"];
                let cachedObservable: Observable<any> = StaticCache.getOrFetch(url, fetcher);
                if (!cachedObservable)
                    throw new Error("Failed loading link: " + url);
                return cachedObservable;
            }

मुझे लगता है कि एक अधिक चतुर तरीका हो सकता है, जो कुछ Observableतरकीबों का उपयोग करेगा लेकिन यह सिर्फ मेरे उद्देश्यों के लिए ठीक था।


0

बस इस कैश लेयर का उपयोग करें, यह वह सब कुछ करता है जिसकी आपको आवश्यकता होती है, और यहां तक ​​कि 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]);
                     }
                     }
                 );
             }
            });
        }
}

0

यह .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);
  }

}

0

आप सरल वर्ग का निर्माण कर सकते हैं Cacheable <> जो कई ग्राहकों के साथ http सर्वर से प्राप्त डेटा को प्रबंधित करने में मदद करता है:

declare type GetDataHandler<T> = () => Observable<T>;

export class Cacheable<T> {

    protected data: T;
    protected subjectData: Subject<T>;
    protected observableData: Observable<T>;
    public getHandler: GetDataHandler<T>;

    constructor() {
      this.subjectData = new ReplaySubject(1);
      this.observableData = this.subjectData.asObservable();
    }

    public getData(): Observable<T> {
      if (!this.getHandler) {
        throw new Error("getHandler is not defined");
      }
      if (!this.data) {
        this.getHandler().map((r: T) => {
          this.data = r;
          return r;
        }).subscribe(
          result => this.subjectData.next(result),
          err => this.subjectData.error(err)
        );
      }
      return this.observableData;
    }

    public resetCache(): void {
      this.data = null;
    }

    public refresh(): void {
      this.resetCache();
      this.getData();
    }

}

प्रयोग

अस्वीकार्य <> वस्तु (संभवतः सेवा के भाग के रूप में):

list: Cacheable<string> = new Cacheable<string>();

और हैंडलर:

this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string[]);
}

एक घटक से कॉल करें:

//gets data from server
List.getData().subscribe(…)

आपके पास इसके लिए कई घटक हो सकते हैं।

अधिक विवरण और कोड उदाहरण यहां हैं: http://devinstance.net/articles/20171021/rxjs-cacheable


0

आप बस 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
    }
}

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


-4

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

क्योंकि आप जिस वादे से ऑब्जर्वेबल का निर्माण कर रहे हैं 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
// ...

मैंने प्रश्न को संपादित करने के लिए इसे Angular 2 को विशिष्ट बनाया
Angular University
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.