RxJs5でAngularHttpネットワーク呼び出しの結果を共有する正しい方法は何ですか?

315
Angular University 2016-03-29 11:55.

Httpを使用して、ネットワーク呼び出しを行い、httpobservableを返すメソッドを呼び出します。

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

これを観察可能にして、複数のサブスクライバーを追加すると、次のようになります。

let network$ = getCustomer();

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

私たちがやりたいのは、これによって複数のネットワーク要求が発生しないようにすることです。

これは珍しいシナリオのように見えるかもしれませんが、実際には非常に一般的です。たとえば、呼び出し元がオブザーバブルにサブスクライブしてエラーメッセージを表示し、非同期パイプを使用してテンプレートに渡す場合、すでに2つのサブスクライバーがあります。

RxJs 5でそれを行う正しい方法は何ですか?

つまり、これは正常に機能しているようです。

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

しかし、これはRxJs 5でこれを行う慣用的な方法ですか、それとも代わりに何か他のことをする必要がありますか?

注:Angular 5 newによるHttpClientと、.map(res => res.json())JSONの結果がデフォルトで想定されるようになったため、すべての例の部分は役に立たなくなりました。

20 answers

234
Günter Zöchbauer 2016-03-30 07:56.

データをキャッシュし、利用可能な場合はキャッシュして、それ以外の場合は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です。

45
Angular University 2016-03-30 12:09.

@Cristianの提案によると、これはHTTPオブザーバブルでうまく機能する方法のひとつであり、一度だけ発行してから完了する方法です。

getCustomer() {
    return this.http.get('/someUrl')
        .map(res => res.json()).publishLast().refCount();
}
37
Guojian Miguel Wu 2017-03-23 15:28.

更新:Ben Leshによると、5.2.0以降の次のマイナーリリースでは、shareReplay()を呼び出すだけで真にキャッシュできるようになります。

以前.....

まず、share()またはpublishReplay(1).refCount()を使用しないでください。これらは同じであり、問​​題は、オブザーバブルがアクティブなときに接続が確立された場合にのみ共有され、完了後に接続した場合にのみ共有されることです。 、実際にはキャッシングではなく、新しいオブザーバブル、変換を作成します。

Birowskiは、ReplaySubjectを使用するという上記の正しい解決策を示しました。この場合、ReplaySubjectは指定された値(bufferSize)をキャッシュします。refCountがゼロに達して新しい接続を確立すると、share()のような新しいオブザーバブルは作成されません。これはキャッシュの正しい動作です。

これが再利用可能な関数です

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

以下は、キャッシュ可能な関数のより高度なバージョンです。これにより、独自のルックアップテーブルとカスタムルックアップテーブルを提供する機能が可能になります。このように、上記の例のようにthis._cacheをチェックする必要はありません。また、最初の引数としてobservableを渡す代わりに、observableを返す関数を渡すことに注意してください。これは、Angularの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")
32
Arlo 2017-05-13 07:15.

rxjs 5.4.0には、新しいshareReplayメソッドがあります。

著者は、「AJAX結果のキャッシュなどを処理するのに理想」と明確に述べています。

rxjs PR#2443 feat(shareReplay):のshareReplayバリアントを追加publishReplay

shareReplayは、ReplaySubjectを介してマルチキャストされたソースであるオブザーバブルを返します。その再生対象は、ソースからのエラー時にリサイクルされますが、ソースの完了時にはリサイクルされません。これにより、shareReplayは再試行可能であるため、AJAX結果のキャッシュなどの処理に最適です。ただし、繰り返し動作は、ソースオブザーバブルを繰り返さず、ソースオブザーバブルの値を繰り返すという点で共有とは異なります。

27
Ivan 2016-06-11 10:17.

この記事によると

publishReplay(1)とrefCountを追加することで、オブザーバブルにキャッシュを簡単に追加できることがわかりました。

その 内部のif文だけ追記

.publishReplay(1)
.refCount();

.map(...)

19
Igor 2019-03-01 10:53.

rxjsバージョン5.4.0(2017-05-09)は、shareReplayのサポートを追加します

shareReplayを使用する理由

通常、複数のサブスクライバー間で実行したくない副作用や負担の計算がある場合は、shareReplayを使用します。また、以前に発行された値にアクセスする必要があるストリームへのサブスクライバーが遅れることがわかっている状況でも役立つ場合があります。サブスクリプションで値を再生するこの機能が、shareとshareReplayの違いです。

これを使用するようにAngularサービスを簡単に変更し、キャッシュされた結果でオブザーバブルを返すことができます。これにより、http呼び出しが1回だけ行われます(最初の呼び出しが成功したと仮定)。

Angularサービスの例

これは、を使用する非常に単純なカスタマーサービスです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
Daniel Birowsky Popeski 2016-03-30 11:59.

質問にスターを付けましたが、これを試してみます。

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

これが証拠です:)

要点は1つだけです。 getCustomer().subscribe(customer$)

のAPI応答をサブスクライブしていません。getCustomer()別のオブザーバブルをサブスクライブすることもできる、オブザーバブルであるReplaySubjectをサブスクライブしており、(これは重要です)最後に発行された値を保持し、そのいずれかに再公開します(ReplaySubjectの)サブスクライバー。

8
allenhwkim 2016-07-27 12:25.

http getの結果をsessionStorageに保存し、それをセッションに使用して、サーバーを二度と呼び出さないようにする方法を見つけました。

使用制限を回避するために、これを使用してgithubAPIを呼び出しました。

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

参考までに、sessionStorageの制限は5M(または4.75M)です。したがって、大量のデータセットに対してこのように使用しないでください。

------編集-------------
sessionStorageの代わりにメモリデータを使用する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();
        });
    }
  }
}
5
ObjectiveTC 2017-09-02 21:46.

Rxjsオブザーバー/オブザーバブル+キャッシング+サブスクリプションを使用したキャッシュ可能なHTTP応答データ

以下のコードを参照してください

*免責事項:私はrxjsを初めて使用するため、observable / observerアプローチを誤用している可能性があることに注意してください。私の解決策は、私が見つけた他の解決策の純粋な集合体であり、十分に文書化された単純な解決策を見つけられなかった結果です。したがって、他の人に役立つことを期待して、完全なコードソリューションを提供しています(見つけたかったのですが)。

*このアプローチは大まかにGoogleFirebaseObservablesに基づいていることに注意してください。残念ながら、私は彼らが内部で行ったことを再現するための適切な経験/時間が不足しています。ただし、以下は、キャッシュ可能なデータへの非同期アクセスを提供する単純な方法です。

状況:「product-list」コンポーネントは、製品のリストを表示するタスクがあります。このサイトは、ページに表示されている製品を「フィルタリング」するいくつかのメニューボタンを備えた単一ページのWebアプリです。

解決策:コンポーネントはサービスメソッドに「サブスクライブ」します。serviceメソッドは、コンポーネントがサブスクリプションコールバックを介してアクセスする製品オブジェクトの配列を返します。serviceメソッドは、そのアクティビティを新しく作成されたオブザーバーにラップし、オブザーバーを返します。このオブザーバー内で、キャッシュされたデータを検索し、それをサブスクライバー(コンポーネント)に返し、戻ります。それ以外の場合は、http呼び出しを発行してデータを取得し、応答をサブスクライブします。ここで、そのデータを処理し(たとえば、データを独自のモデルにマップし)、データをサブスクライバーに返します。

コード

product-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でローカルに実行されているノードRESTサービスを呼び出します)。次にクリックして製品の「フィルタリングされた」ビューに移動すると、製品はキャッシュにあります。

私の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応答データを実装するために私が(これまでに)見つけた最も簡単な方法です。私のAngularアプリでは、製品の異なるビューに移動するたびに、製品リストコンポーネントがリロードされます。ProductServiceは共有インスタンスのようであるため、ProductServiceの 'products:Product []'のローカルキャッシュはナビゲーション中に保持され、その後の「GetProducts()」の呼び出しはキャッシュされた値を返します。最後に、「メモリリーク」を防ぐために、終了時にオブザーバブル/サブスクリプションを閉じる必要がある方法についてのコメントを読みました。これはここには含めていませんが、覚えておくべきことがあります。

5
Arlo 2017-05-10 10:38.

選択する実装は、unsubscribe()で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;
}
3
Burak Tasci 2017-05-03 21:03.

私がいることを前提とし@ NGX-キャッシュ/コアは、 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());
}

方法(キャッシュを使用する可能性もありますhasgetset使用した)キャッシュ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';
  }
}

クライアント側とサーバー側の両方のキャッシュ用のパッケージのリストは次のとおりです。

2
cyberpirate92 2018-03-24 15:36.

私たちがやりたいのは、これによって複数のネットワーク要求が発生しないようにすることです。

私の個人的なお気に入りは、asyncネットワーク要求を行う呼び出しのメソッドを利用することです。メソッド自体は値を返しません。代わりにBehaviorSubject、コンポーネントがサブスクライブする同じサービス内でを更新します。

BehaviorSubjectでは、なぜの代わりにを使用するのObservableですか?なぜなら、

  • サブスクリプション時にBehaviorSubjectは最後の値を返しますが、通常のobservableは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:通常、コンポーネントが破壊された場合は、サブスクリプションを削除することをお勧めします。そのため、この回答で提案されている方法を使用できます。

2
Jay Modi 2018-03-30 05:54.

素晴らしい答え。

または、これを行うことができます:

これは最新バージョンのrxjsからのものです。5.5.7バージョンのRxJSを使用しています

import {share} from "rxjs/operators";

this.http.get('/someUrl').pipe(share());
1
Arlo 2017-04-22 14:26.

rxjs 5.3.0

私は満足していません .map(myFunction).publishReplay(1).refCount()

複数のサブスクライバーがある場合、場合によって.map()myFunction2回実行されます(1回だけ実行されると思います)。1つの修正は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
Matjaz Hirsman 2017-11-15 12:40.

それは、.publishReplay(1).refCount();または.publishLast().refCount();AngularHttpオブザーバブルがリクエスト後に完了するためです。

この単純なクラスは結果をキャッシュするため、.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);
  }

}
1
yfranz 2017-11-27 18:26.

複数のサブスクライバーを持つhttpサーバーから取得したデータの管理に役立つ単純なクラスCacheable <>を作成できます。

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
surfealokesea 2016-05-23 23:43.

マップの後、サブスクライブする前にshare()を呼び出すだけです。

私の場合、残りの呼び出しを行い、データを抽出し、エラーをチェックし、オブザーバブルを具体的な実装サービス(f.ex。:ContractClientService.ts)に返し、最後にこの具体的な実装を行う汎用サービス(RestClientService.ts)がありますobservableをdeContractComponent.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
Ondra Žižka 2016-12-17 04:40.

キャッシュクラスを作成しました。

/**
 * 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
Ravinder Payal 2017-01-24 00:52.

このキャッシュレイヤーを使用するだけで、必要なすべてを実行し、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
Tushar Walzade 2018-11-20 04:26.

単にngx-cacheableを使用できます!それはあなたのシナリオによりよく合います。

これを使用する利点

  • Rest APIを1回だけ呼び出し、応答をキャッシュして、次のリクエストに対して同じものを返します。
  • 作成/更新/削除操作後に必要に応じて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
    }
}

詳細については、こちらのリンクをご覧ください。

Related questions

MORE COOL STUFF

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire は、彼女が息子の Shelby Blackstock と共有する「楽しい」クリスマスの伝統を明らかにしました:「私たちはたくさん笑います」

Reba McEntire が息子の Shelby Blackstock と共有しているクリスマスの伝統について学びましょう。

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルは、自然な髪のスタイリングをめぐってマライア・キャリーと結ばれました

メーガン・マークルとマライア・キャリーが自然な髪の上でどのように結合したかについて、メーガンの「アーキタイプ」ポッドキャストのエピソードで学びましょう.

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子は家族との関係を修復できるという「希望を持っている」:「彼は父親と兄弟を愛している」

ハリー王子が家族、特にチャールズ王とウィリアム王子との関係について望んでいると主張したある情報源を発見してください。

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドは、パニックに陥った休暇の瞬間に、彼女がジャッド家の家長であることを認識しました

ワイノナ・ジャッドが、母親のナオミ・ジャッドが亡くなってから初めての感謝祭のお祝いを主催しているときに、彼女が今では家長であることをどのように認識したかを学びましょう.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

世界最小のマルチツールを15ドルでキーチェーンに追加

世界最小のマルチツールを15ドルでキーチェーンに追加

爪| $ 15 | マルボロ&ケインザクロー| $ 15 | Malboro&Kane世界最小のマルチツールであるTheClawをたった15ドルで手に入れましょう。男の子と一緒に冷たいものを割って開けたり、ネジを締めたり、Amazonパッケージを細断したりする場合でも、この悪い男の子はこれまでで最も便利な製品の1つです。

ワンダーウーマンの続編の悪役についてのより多くの噂

ワンダーウーマンの続編の悪役についてのより多くの噂

ワンダーウーマンとしてのガル・ガドット。WaywardPinesは正式に終了しました。

42,000試合で作られた球がスローモーションで燃えるのを見るのは魅力的な光景です

42,000試合で作られた球がスローモーションで燃えるのを見るのは魅力的な光景です

人が2つのマッチを接着するように導くものは何ですか?何があなたをマッチに打ち続け、構造が湾曲していることを発見するのですか?42,000のボールを作るまで、試合を続けなければならない理由は何ですか?しかし、何よりも、これほど魅力的なショーである可能性はどのようにありますか?私はあなたをだますつもりはありません、次に私たちがあなたに残すのは、あなたや私のような人が自由な時間を過ごすことを決定するビデオですボールを作るために必要な試合数を確認します(答えは42です。

サクラメントビーが1950万人のカリフォルニア州の有権者記録を漏らし、ハッカーによって即座に侵害された

サクラメントビーが1950万人のカリフォルニア州の有権者記録を漏らし、ハッカーによって即座に侵害された

写真:AP先月、カリフォルニアの地元新聞が1,900万件以上の有権者記録をオンラインで公開しました。Gizmodoは今週、明らかなランサムウェア攻撃中にレコードが侵害されたことを確認しました。

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

米国のフィギュア スケートは、チーム イベントでの最終決定の欠如に「苛立ち」、公正な裁定を求める

ロシアのフィギュアスケーター、カミラ・バリエバが関与したドーピング事件が整理されているため、チームは2022年北京冬季オリンピックで獲得したメダルを待っています。

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

Amazonの買い物客は、わずか10ドルのシルクの枕カバーのおかげで、「甘やかされた赤ちゃんのように」眠れると言っています

何千人ものAmazonの買い物客がMulberry Silk Pillowcaseを推奨しており、現在販売中. シルクの枕カバーにはいくつかの色があり、髪を柔らかく肌を透明に保ちます。Amazonで最大46%オフになっている間にシルクの枕カバーを購入してください

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

パデュー大学の教授が覚醒剤を扱った疑いで逮捕され、女性に性的好意を抱かせる

ラファイエット警察署は、「不審な男性が女性に近づいた」という複数の苦情を受けて、12 月にパデュー大学の教授の捜査を開始しました。

コンセプト ドリフト: AI にとって世界の変化は速すぎる

コンセプト ドリフト: AI にとって世界の変化は速すぎる

私たちの周りの世界と同じように、言語は常に変化しています。以前の時代では、言語の変化は数年または数十年にわたって発生していましたが、現在では数日または数時間で変化する可能性があります。

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

犯罪擁護派のオークランドが暴力犯罪者のロミオ・ロレンゾ・パーハムを釈放

SF攻撃で91歳のアジア人女性が殴られ、コンクリートに叩きつけられた

認知症を患っている 91 歳のアジア人女性が最近、47 番街のアウター サンセット地区でロメオ ロレンゾ パーハムに襲われました。伝えられるところによると、被害者はサンフランシスコの通りを歩いていたところ、容疑者に近づき、攻撃を受け、暴行を受けました。

ℝ

“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.

メリック・ガーランドはアメリカに失敗しましたか?

バイデン大統領の任期の半分以上です。メリック・ガーランドは何を待っていますか?

メリック・ガーランドはアメリカに失敗しましたか?

人々にチャンスを与えることは、人生で少し遅すぎると私は信じています。寛大に。

Language