วิธีที่ถูกต้องในการแบ่งปันผลของการโทรเครือข่าย Angular Http ใน RxJs 5 คืออะไร?


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 pipe เรามีสมาชิกสองคนแล้ว

วิธีที่ถูกต้องในการทำเช่นนั้นใน RxJs 5 คืออะไร?

คือนี่ดูเหมือนจะทำงานได้ดี:

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

แต่นี่เป็นวิธีที่ใช้ในการทำสิ่งนี้ใน RxJs 5 หรือว่าเราควรทำอย่างอื่นแทน?

หมายเหตุ: เป็นต่อเชิงมุม 5 ใหม่HttpClientที่.map(res => res.json())มีส่วนร่วมในทุกตัวอย่างอยู่ในขณะนี้ไม่ได้ผลเป็นผล JSON จะสันนิษฐานในขณะนี้โดยค่าเริ่มต้น


1
> share นั้นเหมือนกับการประกาศ () refCount () จริงๆแล้วมันไม่ใช่ ดูการสนทนาต่อไปนี้: github.com/ReactiveX/rxjs/issues/1363
Christian

1
คำถามที่แก้ไขแล้วตามปัญหาดูเหมือนว่าต้องมีการอัปเดตเอกสารในรหัส -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
Angular University

ฉันคิดว่า 'มันขึ้นอยู่กับว่า' แต่สำหรับการโทรที่คุณไม่สามารถแคชข้อมูลในพื้นที่ b / c มันอาจไม่สมเหตุสมผลเนื่องจากการเปลี่ยนพารามิเตอร์ / ชุดค่าผสม. share () ดูเหมือนจะเป็นสิ่งที่ถูกต้อง แต่ถ้าคุณสามารถแคชสิ่งต่าง ๆ ในท้องถิ่นบางคำตอบอื่น ๆ เกี่ยวกับ ReplaySubject / BehaviorSubject ก็เป็นทางเลือกที่ดีเช่นกัน
JimB

ฉันคิดว่าไม่เพียง แต่เราต้องการแคชข้อมูล แต่ยังต้องอัปเดต / แก้ไขข้อมูลแคช มันเป็นกรณีทั่วไป ตัวอย่างเช่นหากฉันต้องการเพิ่มเขตข้อมูลใหม่ลงในโมเดลที่แคชหรืออัปเดตค่าของเขตข้อมูล อาจจะสร้าง Singleton DataCacheService ด้วยวิธีCRUDเป็นวิธีที่ดีกว่า เช่นเดียวกับร้านค้าของRedux คุณคิดอย่างไร?
slideshowp2

คุณสามารถใช้ngx-cacheable ! เหมาะกับสถานการณ์ของคุณมากขึ้น อ้างคำตอบของฉันด้านล่าง
Tushar Walzade

คำตอบ:


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.htmlshareReplayเป็นคำอธิบายที่ดีวิธีการแคชกับ


3
do()ตรงกันข้ามจะmap()ไม่แก้ไขเหตุการณ์ คุณสามารถใช้map()เช่นกัน แต่คุณต้องมั่นใจว่าค่าที่ถูกต้องถูกส่งคืนเมื่อสิ้นสุดการติดต่อกลับ
GünterZöchbauer

3
หากไซต์การโทรที่.subscribe()ไม่ต้องการค่าที่คุณสามารถทำได้เพราะมันอาจได้รับเพียงแค่null(ขึ้นอยู่กับสิ่งที่this.extractDataส่งคืน) แต่ IMHO นี้ไม่ได้แสดงเจตนาของรหัสที่ดี
GünterZöchbauer

2
เมื่อthis.extraDataสิ้นสุดเช่นextraData() { if(foo) { doSomething();}}มิฉะนั้นผลลัพธ์ของนิพจน์สุดท้ายจะถูกส่งคืนซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ
GünterZöchbauer

9
@Günterขอขอบคุณสำหรับรหัสใช้งานได้ อย่างไรก็ตามฉันพยายามที่จะเข้าใจว่าทำไมคุณจึงติดตาม Data และ Observable แยกต่างหาก คุณจะไม่ได้รับผลเหมือนเดิมอย่างมีประสิทธิภาพโดยการแคชเพียงสังเกตได้ <ข้อมูล> เช่นนี้หรือไม่ if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
July.Tech

3
@HarleenKaur เป็นคลาสที่ JSON ที่ได้รับถูก deserialized เพื่อรับการตรวจสอบประเภทที่รัดกุมและการเติมข้อความอัตโนมัติ ไม่จำเป็นต้องใช้มัน แต่เป็นเรื่องปกติ
GünterZöchbauer

44

ตามข้อเสนอแนะของคริสเตียนนี่เป็นวิธีหนึ่งที่ทำงานได้ดีสำหรับ HTTP ที่สามารถสังเกตเห็นได้ซึ่งจะปล่อยเพียงครั้งเดียวและจากนั้นจะเสร็จสมบูรณ์:

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

มีปัญหาสองสามข้อในการใช้วิธีนี้ - สิ่งที่สังเกตได้ที่ส่งคืนไม่สามารถยกเลิกหรือลองใหม่ได้ นี่อาจไม่ใช่ปัญหาสำหรับคุณ แต่ก็อาจเป็นปัญหาอีกครั้ง หากนี่เป็นปัญหาตัวshareดำเนินการอาจเป็นตัวเลือกที่สมเหตุสมผล (แม้ว่าจะมีบางกรณีที่น่ารังเกียจ) สำหรับการสนทนาดำน้ำลึกเกี่ยวกับตัวเลือกดูส่วนความเห็นในโพสต์บล็อกนี้: blog.jhades.org/…
คริสเตียน

1
ความกระจ่างเล็กน้อย ... ถึงแม้ว่าแหล่งที่สังเกตได้จากการแบ่งปันโดยpublishLast().refCount()ไม่สามารถยกเลิกได้ แต่เมื่อการสมัครสมาชิกทั้งหมดที่ส่งคืนโดยการติดตามrefCountถูกยกเลิก แต่ผลกระทบสุทธิคือแหล่งที่สังเกตได้จะยกเลิกการสมัครหากยกเลิกที่ "inflight"
คริสเตียน

@ คริสเตียนเฮ้คุณช่วยอธิบายสิ่งที่คุณหมายถึงด้วยการพูดว่า "ไม่สามารถยกเลิกหรือลองใหม่" ขอบคุณ
ไม่ได้กำหนด

37

UPDATE: เบ็นเลชพูดว่ารุ่นถัดไปหลังจาก 5.2.0 คุณจะสามารถเรียกใช้ shareReplay () เพื่อแคชได้อย่างแท้จริง

ก่อนหน้านี้ .....

ประการแรกอย่าใช้ share () หรือ publishReplay (1) .refCount () พวกเขาเหมือนกันและมีปัญหากับมันคือมันใช้ร่วมกันเฉพาะเมื่อมีการเชื่อมต่อในขณะที่สิ่งที่สังเกตได้นั้นเปิดใช้งานหากคุณเชื่อมต่อหลังจากเสร็จสิ้น มันสร้างสิ่งใหม่ที่สามารถสังเกตได้อีกครั้งการแปลไม่ใช่การแคชจริงๆ

Birowski ให้ทางออกที่ถูกต้องด้านบนซึ่งก็คือใช้ ReplaySubject ReplaySubject จะเก็บค่าที่คุณให้ไว้ (bufferSize) ในกรณีของเรา 1 มันจะไม่สร้างสิ่งที่สังเกตได้ใหม่เช่น share () เมื่อ 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 ของ Angular ดำเนินการทันทีดังนั้นโดยการส่งคืนฟังก์ชันที่ดำเนินการแบบขี้เกียจเราสามารถตัดสินใจว่าจะไม่เรียกมันว่า แคชของเรา

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มีวิธีshareReplayใหม่

ผู้เขียนกล่าวอย่างชัดเจนว่า"เหมาะสำหรับการจัดการสิ่งต่าง ๆ เช่นการแคชผลลัพธ์ AJAX"

rxjs PR # 2443 feat (shareReplay): เพิ่มshareReplayตัวแปรของpublishReplay

shareReplay ส่งคืนสิ่งที่สังเกตได้ซึ่งเป็นแหล่งที่มาแบบหลายช่วงบน ReplaySubject หัวเรื่องการเล่นซ้ำนั้นถูกนำกลับมาใช้ใหม่โดยมีข้อผิดพลาดจากแหล่งที่มา แต่ไม่ใช่เมื่อการทำแหล่งข้อมูลเสร็จสมบูรณ์ สิ่งนี้ทำให้ shareReplay เหมาะอย่างยิ่งสำหรับการจัดการสิ่งต่าง ๆ เช่นการแคชผลลัพธ์ AJAX เนื่องจากสามารถลองใหม่ได้ มันเป็นพฤติกรรมซ้ำ ๆ แต่แตกต่างจากการแบ่งปันซึ่งจะไม่ทำซ้ำแหล่งข้อมูลที่สังเกตได้ แต่มันจะทำซ้ำค่าของแหล่งข้อมูลที่สังเกตได้


มันเกี่ยวข้องกับสิ่งนี้หรือไม่? เอกสารเหล่านี้มาจาก 2014 github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/…
Aaron Hoffman

4
ฉันพยายามเพิ่ม. shareReplay (1, 10,000) ให้กับสิ่งที่สังเกตได้ แต่ฉันไม่ได้สังเกตเห็นการเปลี่ยนแปลงการแคชหรือพฤติกรรม มีตัวอย่างการทำงานหรือไม่
Aydus-Matthew

ดูที่ changelog github.com/ReactiveX/rxjs/blob/ ......มันปรากฏออกมาก่อนหน้านี้ถูกลบใน v5 เพิ่มกลับมาใน 5.4 - ลิงก์ rx-book นั้นอ้างถึง v4 แต่มันมีอยู่ใน LTS v5.5.6 ปัจจุบันและ มันอยู่ใน v6 ฉันนึกภาพลิงก์ rx-book ที่ล้าสมัย
Jason Awbrey

25

ตามบทความนี้

ปรากฎว่าเราสามารถเพิ่มแคชให้กับสิ่งที่สังเกตได้ได้ง่ายโดยการเพิ่ม publishReplay (1) และ refCount

ดังนั้น ข้างในถ้างบเพิ่งต่อท้าย

.publishReplay(1)
.refCount();

ถึง .map(...)


11

รุ่น rxjs 5.4.0 (2017/05/09)เพิ่มการสนับสนุนสำหรับshareReplay

เหตุใดจึงใช้ ShareReplay

โดยทั่วไปคุณต้องการใช้ shareReplay เมื่อคุณมีผลข้างเคียงหรือการคำนวณภาษีที่คุณไม่ต้องการถูกประหารชีวิตในหมู่สมาชิกหลายคน นอกจากนี้ยังอาจมีประโยชน์ในสถานการณ์ที่คุณรู้ว่าคุณจะมีผู้สมัครสมาชิกตอนปลายถึงสตรีมที่ต้องการการเข้าถึงค่าที่ปล่อยออกมาก่อนหน้านี้ ความสามารถในการเล่นซ้ำค่าในการสมัครสมาชิกนี้เป็นสิ่งที่แตกต่างกันแบ่งปันและ shareReplay

คุณสามารถแก้ไขบริการเชิงมุมได้อย่างง่ายดายเพื่อใช้สิ่งนี้และส่งคืนผลลัพธ์ที่สังเกตได้ด้วยผลลัพธ์ที่แคชซึ่งจะทำการโทร http ครั้งเดียวเท่านั้น (สมมติว่าการโทรครั้งที่ 1 ประสบความสำเร็จ)

ตัวอย่างบริการเชิงมุม

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เท่านั้น

นอกจากนี้สมมติฐานที่นี่คือข้อมูลเริ่มต้นที่ส่งคืนไม่ได้ค้างในช่วงชีวิตของอินสแตนซ์ของแอปพลิเคชัน


ฉันชอบรูปแบบนี้มากและกำลังมองหาที่จะใช้มันในไลบรารีที่แบ่งใช้ของบริการ API ที่ฉันใช้ในแอพพลิเคชั่นจำนวนมาก ตัวอย่างหนึ่งคือ UserService และทุกที่ยกเว้นสถานที่สองแห่งไม่จำเป็นต้องทำให้แคชในช่วงอายุการใช้งานของแอพพลิเคชั่นลดลง แต่สำหรับกรณีเหล่านี้ฉันจะยกเลิกการทำให้เป็นโมฆะได้อย่างไร
SirTophamHatt

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

นี่คือข้อพิสูจน์ :)

มีเพียงหนึ่ง Takeaway ที่: getCustomer().subscribe(customer$)

เราไม่ได้สมัครสมาชิกกับการตอบสนองของ api getCustomer()เรากำลังสมัครสมาชิก ReplaySubject ซึ่งสามารถสังเกตได้ซึ่งยังสามารถสมัครสมาชิก Observable ที่แตกต่างกันและ (และนี่เป็นสิ่งสำคัญ) ถือเป็นค่าที่ปล่อยออกมาครั้งสุดท้ายและเผยแพร่ต่อไป ) สมาชิก


1
ฉันชอบวิธีนี้เนื่องจากใช้ประโยชน์จาก rxjs และไม่จำเป็นต้องเพิ่มตรรกะที่กำหนดเองขอบคุณ
Thibs

7

ฉันพบวิธีที่จะเก็บ http รับผลลัพธ์ไว้ใน sessionStorage และใช้สำหรับเซสชันเพื่อที่จะไม่เรียกเซิร์ฟเวอร์อีกครั้ง

ฉันใช้มันเพื่อเรียก 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 ซึ่งใช้ข้อมูลหน่วยความจำแทน 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();
        });
    }
  }
}

หากคุณจะเก็บไว้ในที่เก็บข้อมูลเซสชันคุณจะแน่ใจได้อย่างไรว่าที่เก็บข้อมูลของเซสชันถูกทำลายเมื่อคุณออกจากแอป
Gags

แต่สิ่งนี้นำเสนอพฤติกรรมที่ไม่คาดคิดสำหรับผู้ใช้ เมื่อผู้ใช้กดปุ่ม F5 หรือรีเฟรชเบราว์เซอร์เขาก็คาดหวังว่าข้อมูลใหม่จากเซิร์ฟเวอร์ แต่จริงๆแล้วเขากำลังรับข้อมูลที่ล้าสมัยจาก localStorage รายงานข้อผิดพลาดตั๋วสนับสนุน ฯลฯ ที่เข้ามา ... ตามชื่อsessionStorageบอกว่าฉันจะใช้เฉพาะกับข้อมูลที่คาดว่าจะสอดคล้องกันสำหรับเซสชันทั้งหมด
Martin Schneider

@ MA-Maddin ตามที่ฉันระบุว่า "ฉันใช้เพื่อหลีกเลี่ยงการ จำกัด การใช้งาน" ถ้าคุณต้องการให้ข้อมูลถูกรีเฟรชด้วย F5 คุณจะต้องใช้หน่วยความจำแทน sessionStorage คำตอบได้รับการแก้ไขด้วยวิธีการนี้
allenhwkim

ใช่ว่าอาจเป็นกรณีการใช้งาน ฉันเพิ่งถูกทริกเกอร์เนื่องจากทุกคนกำลังพูดถึงCacheและ OP มีgetCustomerอยู่ในตัวอย่างของเขา ;) ดังนั้นเพียงแค่อยากจะเตือน ppl บางอย่างที่อาจไม่เห็นความเสี่ยงที่ :)
มาร์ตินชไนเดอ

5

การใช้งานที่คุณเลือกนั้นจะขึ้นอยู่กับว่าคุณต้องการยกเลิกการสมัคร () เพื่อยกเลิกคำขอ HTTP หรือไม่

ไม่ว่าในกรณีใดผู้ตกแต่ง TypeScriptเป็นวิธีที่ดีในการกำหนดมาตรฐานการทำงาน นี่คือสิ่งที่ฉันเขียน:

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

สวัสดี @Arlo - ตัวอย่างข้างต้นไม่ได้รวบรวม จากบรรทัดProperty 'connect' does not exist on type '{}'. returnValue.connect();คุณสามารถทำอย่างละเอียด?
กีบ

4

ข้อมูลการตอบสนอง HTTP ที่แคชได้โดยใช้ Rxjs Observer / Observable + Caching + การสมัครสมาชิก

ดูรหัสด้านล่าง

* ข้อจำกัดความรับผิดชอบ: ฉันใหม่สำหรับ rxjs ดังนั้นโปรดจำไว้ว่าฉันอาจใช้วิธีการสังเกตการณ์ / ผู้สังเกตการณ์ในทางที่ผิด โซลูชันของฉันคือการรวมกลุ่มของโซลูชันอื่นที่ฉันพบอย่างหมดจดและเป็นผลมาจากการล้มเหลวในการค้นหาโซลูชันที่มีเอกสารที่เรียบง่าย ดังนั้นฉันจึงนำเสนอโซลูชันรหัสที่สมบูรณ์ของฉัน (ตามที่ฉันต้องการจะพบ) โดยหวังว่าจะช่วยให้ผู้อื่น

* หมายเหตุวิธีนี้ขึ้นอยู่กับ GoogleFirebaseObservables น่าเสียดายที่ฉันไม่มีประสบการณ์ / เวลาที่เหมาะสมในการทำซ้ำสิ่งที่พวกเขาทำภายใต้ประทุน แต่ต่อไปนี้เป็นวิธีที่ง่ายในการจัดเตรียมการเข้าถึงแบบอะซิงโครนัสกับข้อมูลแคชบางตัว

สถานการณ์ : คอมโพเนนต์ 'รายการผลิตภัณฑ์' ได้รับมอบหมายพร้อมแสดงรายการผลิตภัณฑ์ ไซต์ดังกล่าวเป็นเว็บแอปเดียวที่มีปุ่มเมนูบางปุ่มที่จะ 'กรอง' ผลิตภัณฑ์ที่แสดงบนหน้า

การแก้ไข : ส่วนประกอบ "สมัครสมาชิก" กับวิธีการบริการ วิธีการบริการส่งกลับอาร์เรย์ของวัตถุผลิตภัณฑ์ซึ่งส่วนประกอบเข้าถึงผ่านการเรียกกลับการสมัครสมาชิก วิธีการบริการล้อมรอบกิจกรรมใน Observer ที่สร้างขึ้นใหม่และส่งคืนผู้สังเกตการณ์ ภายในผู้สังเกตการณ์นี้มันจะค้นหาข้อมูลที่เก็บไว้และส่งกลับไปยังสมาชิก (ส่วนประกอบ) และส่งกลับ มิเช่นนั้นจะมีการเรียก 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 ซึ่งลดความซับซ้อนของรหัสและลด 'โอเวอร์เฮด' ลงอย่างมาก ใน products.service.ts 1. นำเข้า {BehaviorSubject} จาก 'rxjs'; 2. เปลี่ยน 'ผลิตภัณฑ์: ผลิตภัณฑ์ []' เป็น 'ผลิตภัณฑ์ $: BehaviorSubject <ผลิตภัณฑ์ []> = ใหม่ BehaviorSubject <ผลิตภัณฑ์ []> ([]);' 3. ตอนนี้คุณสามารถโทรหา http ได้โดยไม่ต้องคืนอะไรเลย http_getProducts () {this.http.get (... ). map (res => res.json ()) สมัครสมาชิก (products => this.product $ .next (ผลิตภัณฑ์))};
ObjectiveTC

1
ตัวแปรท้องถิ่น 'product $' เป็น behaviorSubject ซึ่งทั้ง EMIT และ STORE จะเป็นผลิตภัณฑ์ล่าสุด (จากผลิตภัณฑ์ $ .next (.. ) ในส่วนที่ 3) ในส่วนของคุณฉีดบริการตามปกติ คุณได้รับค่าผลิตภัณฑ์ $ ที่ได้รับมอบหมายล่าสุดโดยใช้ productService.product $ .value หรือสมัครสมาชิกผลิตภัณฑ์ $ หากคุณต้องการดำเนินการทุกครั้งที่ผลิตภัณฑ์ $ ได้รับค่าใหม่ (เช่นฟังก์ชัน $ .next (... ) ถูกเรียกในส่วนที่ 3)
ObjectiveTC

1
เช่นใน products.component.ts ... this.productService.product $ .takeUntil (this.ngUnsubscribe) .subscribe ((ผลิตภัณฑ์) => {this.category); ให้ filteredProducts = this.productService.getProductsByCategory (this.category); this.products = filteredProducts; });
ObjectiveTC

1
หมายเหตุสำคัญเกี่ยวกับการยกเลิกการสมัครจากสิ่งที่สังเกตได้: ".takeUntil (this.ngUnsubscribe)" ดูคำถาม / คำตอบล้นสแต็กนี้ซึ่งดูเหมือนจะแสดงวิธีแนะนำ 'de-พฤตินัย' เพื่อยกเลิกการสมัครจากเหตุการณ์: stackoverflow.com/questions/38008334/…
ObjectiveTC

1
ทางเลือกคือ. .first () หรือ. Take (1) หากการสังเกตการณ์นั้นหมายถึงการรับข้อมูลเพียงครั้งเดียวเท่านั้น ทั้งหมด 'ลำธารที่ไม่มีที่สิ้นสุด' อื่น ๆ ของสิ่งที่สังเกตได้ควรยกเลิกการสมัครใน 'ngOnDestroy ()' และหากคุณไม่ทำเช่นนั้นคุณอาจสิ้นสุดด้วยการเรียกกลับ 'ที่สังเกตได้' ซ้ำกัน stackoverflow.com/questions/28007777/…
ObjectiveTC

3

ฉันคิดว่า@ ngx-cache / coreอาจมีประโยชน์ในการรักษาคุณลักษณะการแคชสำหรับการโทร http โดยเฉพาะอย่างยิ่งถ้าการโทร HTTP นั้นเกิดขึ้นทั้งบนเบราว์เซอร์และแพลตฟอร์มเซิร์ฟเวอร์

สมมติว่าเรามีวิธีการดังต่อไปนี้:

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

คุณสามารถใช้Cachedมัณฑนากรของ@ NGX แคช / หลักในการจัดเก็บค่าที่ส่งกลับจากวิธีการโทร HTTP ที่cache storage( สามารถกำหนดค่าได้โปรดตรวจสอบการดำเนินงานที่ng เมล็ด / สากล ) - สิทธิในการดำเนินการครั้งแรก ครั้งต่อไปวิธีการที่จะเรียก (เรื่องที่ไม่มีในเบราว์เซอร์หรือเซิร์ฟเวอร์แพลตฟอร์ม) ค่าจะถูกดึงจากstoragecache 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) การใช้แคช API

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()และทำให้ Observable ร้อนทันที:

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

นี่จะเริ่มคำขอ HTTP โดยไม่คำนึงถึงผู้สมัครสมาชิก ฉันไม่แน่ใจว่าการยกเลิกการเป็นสมาชิกก่อนที่ HTTP GET จะเสร็จสิ้นจะยกเลิกหรือไม่


1

สิ่งที่เราต้องการจะทำคือมั่นใจได้ว่านี่จะไม่ทำให้เกิดคำขอเครือข่ายหลาย

รายการโปรดส่วนตัวของฉันคือการใช้asyncวิธีการโทรที่ร้องขอเครือข่าย วิธีการเหล่านั้นจะไม่ส่งคืนค่า แต่เป็นการอัปเดตBehaviorSubjectภายในบริการเดียวกันซึ่งองค์ประกอบจะสมัครรับข้อมูลแทน

ตอนนี้ใช้BehaviorSubjectแทนของObservableทำไม? เพราะ,

  • เมื่อสมัครสมาชิก BehaviorSubject onnextส่งกลับค่าสุดท้ายขณะที่ปกติทริกเกอร์เท่านั้นที่สังเกตได้เมื่อมันได้รับ
  • หากคุณต้องการดึงค่าสุดท้ายของ BehaviorSubject ในโค้ดที่ไม่สามารถสังเกตได้ (โดยไม่ต้องสมัครสมาชิก) คุณสามารถใช้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โดยใช้วิธีการนี้เราไม่ได้มีการเก็บข้อมูลอย่างชัดเจนระหว่างสายเครือข่ายที่ตามมาเป็นก็จัดการโดย

PS:โดยปกติเมื่อองค์ประกอบได้รับการทำลายมันเป็นวิธีที่ดีในการกำจัดการสมัครเพื่อที่คุณสามารถใช้วิธีการที่แนะนำในนี้คำตอบ


1

คำตอบที่ดี

หรือคุณสามารถทำสิ่งนี้:

นี่คือจาก rxjs รุ่นล่าสุด ฉันใช้RxJSรุ่น5.5.7

import {share} from "rxjs/operators";

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

0

โทรเพียงหุ้น ()หลังจากแผนที่และก่อนที่จะสมัครเป็นสมาชิก

ในกรณีของฉันฉันมีบริการทั่วไป (RestClientService.ts) ซึ่งกำลังโทรหาที่เหลือดึงข้อมูลตรวจสอบข้อผิดพลาดและกลับมาให้บริการการดำเนินงานที่เป็นรูปธรรม (f.ex .: ContractClientService.ts) ในที่สุดการใช้งานที่เป็นรูปธรรมนี้ ส่งกลับที่สามารถสังเกตได้เพื่อ de ContractComponent.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>();
    }

}

ทุกอย่างคงที่เพราะวิธีที่เราใช้ แต่รู้สึกฟรีเพื่อให้เป็นคลาสปกติและบริการ ฉันไม่แน่ใจว่า Angular รักษาอินสแตนซ์เดียวตลอดเวลาหรือไม่ (ใหม่ถึง 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

เพียงใช้เลเยอร์แคชนี้มันทำทุกสิ่งที่คุณต้องการและจัดการแคชสำหรับคำขออาแจ็กซ์

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();ตั้งแต่ Angular Http observables เสร็จสมบูรณ์หลังจากที่ร้องขอ

คลาสแบบง่ายนี้แคชผลลัพธ์เพื่อให้คุณสามารถสมัครเป็นสมาชิก. 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();
    }

}

การใช้

ประกาศ Cacheable <> วัตถุ (สมมุติว่าเป็นส่วนหนึ่งของบริการ):

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 ! เหมาะกับสถานการณ์ของคุณมากขึ้น

ประโยชน์ของการใช้สิ่งนี้

  • มันเรียก API ส่วนที่เหลือเพียงครั้งเดียวแคชตอบสนองและผลตอบแทนที่เหมือนกันสำหรับการร้องขอดังต่อไปนี้
  • สามารถเรียก API ตามต้องการหลังจากการสร้าง / อัปเดต / ลบ

ดังนั้นคลาสบริการของคุณจะเป็นแบบนี้ -

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

คุณลองใช้รหัสที่คุณมีอยู่แล้วหรือยัง?

เนื่องจากคุณกำลังสร้าง Observable จากสัญญาที่เกิดจาก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
// ...

ฉันได้แก้ไขคำถามเพื่อให้เป็นภาษาอังกฤษเฉพาะ 2
มหาวิทยาลัย Angular
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.