codememo

RxJs 5에서 Angular Http 네트워크 호출의 결과를 공유하는 올바른 방법은 무엇입니까?

tipmemo 2023. 5. 18. 21:04
반응형

RxJs 5에서 Angular Http 네트워크 호출의 결과를 공유하는 올바른 방법은 무엇입니까?

Http를 사용하여 네트워크 호출을 수행하고 http 관찰 가능한 값을 반환하는 메서드를 호출합니다.

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

이 기능을 사용하여 여러 가입자를 추가할 경우:

let network$ = getCustomer();

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

이로 인해 여러 네트워크 요청이 발생하지 않도록 해야 합니다.

예를 들어, 호출자가 오류 메시지를 표시하기 위해 관찰 가능 항목에 가입하고 비동기 파이프를 사용하여 템플릿에 전달하는 경우, 이미 두 개의 가입자가 있습니다.

RxJs 5에서 올바른 방법은 무엇입니까?

즉, 이것은 잘 작동하는 것 같습니다.

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

하지만 이것이 RxJs 5에서 이것을 하는 관용적인 방법입니까, 아니면 우리가 대신 다른 것을 해야 합니까?

참고: Angular 5에 따라 새것HttpClient,.map(res => res.json())JSON 결과가 기본적으로 가정되기 때문에 모든 예제의 부품은 이제 쓸모가 없습니다.

, 은 편집: 2021년판을 사용하는 입니다.shareReplay연산자는 RxJs에 의해 기본적으로 제안되었습니다.자세한 내용은 아래 답변을 참조하십시오.


데이터를 캐시하고 사용 가능한 경우 캐시된 데이터를 반환합니다. 그렇지 않으면 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.

@Cristian 제안에 따르면, 이것은 HTTP 관측 가능한 항목에 잘 작동하는 한 가지 방법이며, 한 번만 내보낸 다음 완료합니다.

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

업데이트: Ben Lesh는 5.2.0 이후의 다음 마이너 릴리스에서는 shareReplay()를 호출하여 진정한 캐쉬를 수행할 수 있다고 말합니다.

이전 이야기...

첫째, share()를 사용하거나 Replay(1)를 게시하지 마십시오. refCount()는 동일하고 문제는 관찰 가능한 연결이 활성화된 상태에서만 공유된다는 것입니다. 완료된 후 연결하면 실제로 캐싱되지 않은 새로운 관찰 가능한 변환이 다시 생성됩니다.

Birowski는 위에서 올바른 솔루션인 ReplaySubject를 사용했습니다.ReplaySubject는 사례 1에서 사용자가 지정한 값(bufferSize)을 캐시합니다.refCount가 0에 도달하고 사용자가 새로운 연결을 만들면 공유()와 같은 새로운 관찰 가능한 변수가 생성되지 않습니다. 이는 캐싱을 위한 올바른 동작입니다.

여기 재사용 가능한 기능이 있습니다.

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

아래는 캐시 가능한 기능의 고급 버전입니다. 이 기능을 사용하면 자체 룩업 테이블 + 사용자 정의 룩업 테이블을 제공할 수 있습니다.이렇게 하면, 당신은 이것을 확인할 필요가 없습니다._cache는 위의 예와 같습니다.또한 첫 번째 인수로 관찰 가능한 값을 전달하는 대신 관찰 가능한 값을 반환하는 함수를 전달합니다. 이는 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")

rxjs 5.4.0에는 새 shareReplay 메서드가 있습니다.

저자는 "AJAX 결과 캐싱과 같은 것을 처리하기에 이상적"이라고 명시적으로 말합니다.

PR #): rxjs PR #2443 feature(shareReplay)를 추가합니다. 추가shareReplaypublishReplay

shareReplay는 ReplaySubject를 통해 멀티캐스트된 소스인 관찰 가능한 항목을 반환합니다.해당 재생 제목은 원본에서 오류가 발생하면 재생되지만 원본 완료 시에는 재생되지 않습니다.따라서 shareReplay는 재시도가 가능하기 때문에 AJAX 결과 캐싱과 같은 작업을 처리하는 데 이상적입니다.그러나 반복 동작은 관측 가능한 소스를 반복하지 않고 관측 가능한 소스 값을 반복한다는 점에서 공유와 다릅니다.

기사에 의하면

publishReplay(1) 및 refCount를 추가하면 관찰 가능한 항목에 캐싱을 쉽게 추가할 수 있는 것으로 나타났습니다.

그래서 만약 진술이 추가된다면 내부에서.

.publishReplay(1)
.refCount();

.map(...)

rxjs 버전 5.4.0(2017-05-09)에는 shareReplay에 대한 지원이 추가되었습니다.

shareReplay를 사용하는 이유는 무엇입니까?

일반적으로 여러 가입자 간에 실행하지 않으려는 부작용이나 과중한 계산이 있는 경우 shareReplay를 사용하려고 합니다.또한 이전에 내보낸 값에 액세스해야 하는 스트림에 대한 가입자가 늦어질 것으로 예상되는 경우에도 유용할 수 있습니다.구독에서 값을 재생할 수 있는 이 기능은 공유 및 공유 재생을 차별화합니다.

이를 사용하도록 각도 서비스를 쉽게 수정하고 한 번만 http 호출을 수행하는 캐시된 결과와 함께 관찰 가능한 항목을 반환할 수 있습니다(첫 번째 호출이 성공한 경우).

각도 서비스 예제

여기매우간단고서있습다니가비스객한▁that▁service▁uses다▁simple를 사용하는 매우 간단한 고객 서비스가 .shareReplay.

고객.service.ts

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

@Injectable({providedIn: 'root'})
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그러나 에서 반환된 관찰 가능한 것들이 "차가운"이기 때문에 생성자에서 이것을 하는 것은 허용됩니다. 왜냐하면 http 호출은 오직 첫 번째 호출과 함께 이루어질 것이기 때문입니다.subscribe.

또한 여기서는 초기 반환된 데이터가 애플리케이션 인스턴스의 수명 동안 오래되지 않는다고 가정합니다.

제가 질문을 했는데, 한번 해보겠습니다.

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

여기 그 증거가 있습니다 :)

한 가지 이 있습니다: 단한가방있습이니다법지있다▁there습니:▁takeaway법▁but.getCustomer().subscribe(customer$)

는 api api api의 않습니다.getCustomer()우리는 관찰 가능한 ReplaySubject를 구독하고 있는데, 이는 다른 Observable에도 가입할 수 있으며, 마지막으로 방출된 값을 유지하고(그리고 이것은 중요합니다) ReplaySubject의 모든 구독자에게 다시 게시합니다.

저는 http get 결과를 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();
        });
    }
  }
}

참고로, 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();
        });
    }
  }
}

당신이 선택한 구현은 당신이 당신의 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;
}

Rxjs Observer/Observable + Caching + Subscription을 사용하여 캐시 가능한 HTTP 응답 데이터

아래 코드 참조

*문자:저는 rxjs가 처음이라 관찰 가능한/관찰자 접근법을 잘못 사용할 수 있다는 것을 명심하세요.제 솔루션은 제가 찾은 다른 솔루션들을 순수하게 조합한 것이며, 문서화된 간단한 솔루션을 찾지 못한 결과입니다.따라서 저는 다른 사람들에게 도움이 되기를 바라며 (찾고 싶었던 것처럼) 저의 완전한 코드 솔루션을 제공하고 있습니다.

*참고로 이 접근 방식은 대략 Google Firebase Observables를 기반으로 합니다.불행하게도 저는 그들이 후드 아래에서 했던 일을 재현할 적절한 경험과 시간이 부족합니다.그러나 다음은 캐시 가능한 일부 데이터에 대한 비동기식 액세스를 제공하는 단순한 방법입니다.

상황:'제품 목록' 구성 요소는 제품 목록을 표시하는 작업을 수행합니다.이 사이트는 페이지에 표시된 제품을 '필터링'하는 일부 메뉴 버튼이 있는 단일 페이지 웹 앱입니다.

솔루션:구성 요소는 서비스 메서드를 "구독"합니다.서비스 메서드는 제품 개체의 배열을 반환하며, 구성 요소는 구독 콜백을 통해 이 개체에 액세스합니다.서비스 메서드는 새로 만든 Observer에 활동을 래핑하고 Observer를 반환합니다.이 관찰자 내에서 캐시된 데이터를 검색하여 가입자(구성 요소)에게 다시 전달하고 반환합니다.그렇지 않으면 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 응답 데이터를 구현하는 가장 간단한 방법입니다.각진 앱에서 제품의 다른 보기로 이동할 때마다 제품 목록 구성 요소가 다시 로드됩니다.ProductService는 공유 인스턴스인 것 같습니다. 따라서 ProductService에서 'products:Product[]'의 로컬 캐시는 탐색 중에 유지되고 "GetProducts()"에 대한 후속 호출은 캐시된 값을 반환합니다.마지막으로 '메모리 누수'를 방지하기 위해 완료 시 관찰 가능한/구독을 닫아야 하는 방법에 대한 의견을 읽었습니다.여기에 이것을 포함하지 않았지만, 명심해야 할 사항입니다.

@ngx-cache/core는 특히 HTTP 호출이 브라우저와 서버 플랫폼 모두에서 이루어질 경우 HTTP 호출에 대한 캐싱 기능을 유지하는 데 유용할 수 있다고 생각합니다.

다음과 같은 방법이 있다고 가정합니다.

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

당신은 할 수 .CachedHTTP 호출을 수행하는 메서드에서 반환된 값을 저장하는 @ngx-cache/core의 decoratorcache storage(는 구성할 있습니다. ng-seed/universal에서 구현을 확인하십시오) - 첫 번째 실행 시 바로.다음에 메소드가 호출될 때(브라우저나 서버 플랫폼에 상관없이) 값은cache 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를 사용합니다.

임의의 클래스

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

다음은 클라이언트 측 및 서버 측 캐싱을 위한 패키지 목록입니다.

이로 인해 여러 네트워크 요청이 발생하지 않도록 해야 합니다.

가 개인적으로 좋아하는 것은 ▁of것을 사용하는 입니다.async네트워크 요청을 하는 호출에 대한 메서드입니다.메소드 자체는 값을 반환하지 않고 대신 업데이트합니다.BehaviorSubject동일한 서비스 내에서 어떤 구성 요소를 구독할지 결정합니다.

이제 사용해야 하는 이유BehaviorSubject신에대 Observable 왜하면.

  • 시 , 인 관찰 항목은 "BehaviorSubject" " " " " " " 를 .onnext.
  • ) 할 수 없는 하려면 , 「 」를 할 수 .getValue()방법.

예:

고객.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.

PS: 일반적으로 구성 요소가 손상되면 구독을 제거하는 것이 좋습니다. 이 답변에서 제안한 방법을 사용할 수 있습니다.

좋은 답변입니다.

아니면 이렇게 할 수도 있습니다.

이것은 최신 버전의 rxjs에서 온 것입니다.5.5.7 버전의 RxJS를 사용하고 있습니다.

import {share} from "rxjs/operators";

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

여러 가입자가 있는 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

rxjs 5.3.0

나는 그동안 행복하지 않았습니다..map(myFunction).publishReplay(1).refCount()

가 여러 경우에는 ▁with경우인ers명,▁subscrib..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이 끝나기 전에 구독을 취소할지 모르겠습니다.

그건….publishReplay(1).refCount();또는.publishLast().refCount();요청 후 Angular Http 관찰 가능 항목이 완료되었기 때문입니다.

이 단순 클래스는 결과를 캐시하여 .value를 여러 번 구독하고 요청을 하나만 수행할 수 있도록 합니다..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);
  }

}

위의 대부분의 답변은 입력을 받지 않는 http 요청에 대해서는 괜찮습니다.일부 입력을 사용하여 API 호출을 할 때마다 요청을 새로 만들어야 합니다.위에서 이를 처리할 수 있는 유일한 응답은 @Arlo의 응답입니다.

저는 당신이 같은 입력을 가진 모든 발신자에게 응답을 공유하는 데 사용할 수 있는 약간 더 간단한 장식기를 만들었습니다.Arlo의 답변과 달리 지연된 가입자에 대한 응답을 재생하지 않고 동시 요청을 하나로 처리합니다.지연된 관찰자에 대한 응답(캐시된 응답이라고 함)을 재생하는 것이 목표인 경우 아래 코드를 수정하고 대체할 수 있습니다.share()와 함께shareReplay(1):

https://gist.github.com/OysteinAmundsen/b97a2359292463feb8c0e2270ed6695a

import { finalize, Observable, share } from 'rxjs';

export function SharedObservable(): MethodDecorator {
  const obs$ = new Map<string, Observable<any>>();
  return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const key = JSON.stringify(args);
      if (!obs$.has(key)) {
        // We have no observable for this key yet, so we create one
        const res = originalMethod.apply(this, args).pipe(
          share(), // Make the observable hot
          finalize(() => obs$.delete(key)) // Cleanup when observable is complete
        );
        obs$.set(key, res);
      }
      // Return the cached observable
      return obs$.get(key);
    };
    return descriptor;
  };
}

용도:

@SharedObservable()
myFunc(id: number): Observable<any> {
  return this.http.get<any>(`/api/someUrl/${id}`);
}

지도 구독 전에 공유()호출하면 됩니다.

저의 경우 일반 서비스(RestClientService.ts)가 나머지 전화를 걸고, 데이터를 추출하고, 오류를 확인하고, 구체적인 구현 서비스(f.ex.:ContractClientService.ts), 마지막으로 이 구체적인 구현은 관찰 가능한 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) {
    ...
  }

}

계약 서비스.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);
  }

}

계약 구성 요소.ts:

export class ContractComponent implements OnInit {

  getAllItems() {
    this.rcService.getAllItems().subscribe((data) => {
      this.items = data;
   });
  }

}

저는 캐시 클래스를 작성했고,

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

더방법이 것 , 좀더현방있수을도있것, ▁some▁i,▁use약.Observable속임수를 썼지만 제 목적에 딱 맞았습니다.

이 캐쉬 계층을 사용하면 필요한 모든 작업을 수행할 수 있으며, 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]);
                     }
                     }
                 );
             }
            });
        }
}

단순히 ngx 캐시 가능 기능을 사용할 수 있습니다!그것은 당신의 시나리오에 더 잘 맞습니다.

이 기능을 사용하면 얻을 수

  • rest 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
    }
}

여기 더 많은 참고를 위한 링크가 있습니다.

이미 가지고 있는 코드를 실행해 보셨습니까?

왜냐하면 당신은 관찰 가능한 것을 구성하고 있기 때문입니다.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
// ...

언급URL : https://stackoverflow.com/questions/36271899/what-is-the-correct-way-to-share-the-result-of-an-angular-http-network-call-in-r

반응형