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 new 에 따라 JSON 결과가 기본적으로 가정되므로 모든 예제 HttpClient의 .map(res => res.json())부분은 이제 쓸모가 없습니다.
데이터를 캐시하고 캐시 된 경우 사용 가능한 경우이를 리턴하여 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;
}
}
}
이 artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html 은로 캐시하는 방법에 대한 훌륭한 설명입니다 shareReplay.
@Cristian 제안에 따르면, 이것은 HTTP Observable에 잘 작동하는 한 가지 방법으로 한 번만 방출 한 다음 완료됩니다.
getCustomer() {
return this.http.get('/someUrl')
.map(res => res.json()).publishLast().refCount();
}
업데이트 : Ben Lesh에 따르면 5.2.0 이후의 다음 마이너 릴리스에서는 shareReplay ()를 호출하여 실제로 캐시 할 수 있습니다.
이전 ...
첫째, share () 또는 publishReplay (1) .refCount ()를 사용하지 마십시오. 동일하고 문제는 관찰 가능 상태에서 연결이 완료된 경우에만 연결이 완료되고 연결이 완료된 후에 연결하는 경우에만 공유한다는 것입니다 실제로 캐싱이 아닌 새로운 관측 가능 번역을 다시 만듭니다.
Birowski는 ReplaySubject를 사용하는 올바른 솔루션을 제공했습니다. ReplaySubject는 우리의 경우 1에 주어진 값 (bufferSize)을 캐시합니다. refCount가 0에 도달하고 새로운 연결을 설정하면 캐싱에 대한 올바른 동작 인 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")
rxjs 5.4.0 에는 새로운 shareReplay 메소드가 있습니다.
- rx-book shareReplay ()
- reactx.io/rxjs에 문서가 없습니다.
저자는 "AJAX 결과 캐싱과 같은 것을 다루기에 이상적" 이라고 명시 적으로 말한다
rxjs PR # 2443 feat (shareReplay) :의 shareReplay변형 추가publishReplay
shareReplay는 ReplaySubject를 통해 멀티 캐스트 된 소스 인 Observable을 반환합니다. 해당 재생 주제는 소스의 오류에 따라 재활용되지만 소스의 완료에는 없습니다. 이로 인해 shareReplay는 재시도 가능하기 때문에 AJAX 결과 캐싱과 같은 것을 처리하는 데 이상적입니다. 그러나 반복 동작이지만 소스 관찰 가능을 반복하지 않고 소스 관찰 가능 값을 반복한다는 점에서 공유와 다릅니다.
이 기사 에 따르면
publishReplay (1) 및 refCount를 추가하여 Observable에 캐싱을 쉽게 추가 할 수 있습니다.
그래서 if 문 안에 추가하십시오.
.publishReplay(1)
.refCount();
에 .map(...)
나는 그 질문에 별표를 썼다. 그러나 나는 이것을 시도 할 것이다.
//this will be the shared observable that
//anyone can subscribe to, get the value,
//but not cause an api request
let customer$ = new Rx.ReplaySubject(1);
getCustomer().subscribe(customer$);
//here's the first subscriber
customer$.subscribe(val => console.log('subscriber 1: ' + val));
//here's the second subscriber
setTimeout(() => {
customer$.subscribe(val => console.log('subscriber 2: ' + val));
}, 1000);
function getCustomer() {
return new Rx.Observable(observer => {
console.log('api request');
setTimeout(() => {
console.log('api response');
observer.next('customer object');
observer.complete();
}, 500);
});
}
여기에 증거가 있습니다 :)
테이크 아웃은 한 가지뿐입니다. getCustomer().subscribe(customer$)
우리는의 api 응답 getCustomer()을 구독하지 않고 다른 Observable을 구독 할 수있는 ReplaySubject를 구독하고 있으며 (이것은 중요합니다) 마지막으로 방출 된 값을 유지하고 (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)입니다. 따라서 큰 데이터 세트에는 이와 같이 사용하면 안됩니다.
------ edit -------------
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();
});
}
}
}
rxjs 버전 5.4.0 (2017-05-09) 에 shareReplay 지원이 추가 되었습니다 .
shareReplay를 사용하는 이유는 무엇입니까?
일반적으로 여러 구독자간에 실행하지 않으려는 부작용 또는 과세 계산이있는 경우 shareReplay를 사용하려고합니다. 또한 이전에 방출 된 값에 액세스해야하는 스트림에 늦게 가입자가 있다는 것을 알고있는 상황에서도 유용 할 수 있습니다. 서브 스크립 션에서 가치를 재생하는이 기능은 공유와 shareReplay를 차별화하는 것입니다.
이를 사용하기 위해 각도 서비스를 쉽게 수정하고 캐시 된 결과로 옵저버 블을 반환하여 http 호출을 한 번만 할 수 있습니다 (첫 번째 호출이 성공했다고 가정).
각도 서비스 예
다음은를 사용하는 매우 간단한 고객 서비스입니다 shareReplay.
customer.service.ts
import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class CustomerService {
private readonly _getCustomers: Observable<ICustomer[]>;
constructor(private readonly http: HttpClient) {
this._getCustomers = this.http.get<ICustomer[]>('/api/customers/').pipe(shareReplay());
}
getCustomers() : Observable<ICustomer[]> {
return this._getCustomers;
}
}
export interface ICustomer {
/* ICustomer interface fields defined here */
}
생성자의 할당은 메소드로 이동 될 수 있지만 생성자 getCustomers에서 리턴 된 옵저버 블 HttpClient이 "콜드 (cold)" 이므로 생성자에서이를 수행하는 것은 http 호출이 첫 번째 호출로만 수행되므로 허용됩니다 subscribe.
또한 여기서 반환 된 초기 데이터는 응용 프로그램 인스턴스 수명 동안 오래되지 않습니다.
unsubscribe ()가 HTTP 요청을 취소하도록 하려는지 여부에 따라 선택한 구현이 달라집니다.
어떤 경우, 타이프 라이터의 장식이 동작을 표준화의 좋은 방법입니다. 이것은 내가 쓴 것입니다 :
@CacheObservableArgsKey
getMyThing(id: string): Observable<any> {
return this.http.get('things/'+id);
}
데코레이터 정의 :
/**
* Decorator that replays and connects to the Observable returned from the function.
* Caches the result using all arguments to form a key.
* @param target
* @param name
* @param descriptor
* @returns {PropertyDescriptor}
*/
export function CacheObservableArgsKey(target: Object, name: string, descriptor: PropertyDescriptor) {
const originalFunc = descriptor.value;
const cacheMap = new Map<string, any>();
descriptor.value = function(this: any, ...args: any[]): any {
const key = args.join('::');
let returnValue = cacheMap.get(key);
if (returnValue !== undefined) {
console.log(`${name} cache-hit ${key}`, returnValue);
return returnValue;
}
returnValue = originalFunc.apply(this, args);
console.log(`${name} cache-miss ${key} new`, returnValue);
if (returnValue instanceof Observable) {
returnValue = returnValue.publishReplay(1);
returnValue.connect();
}
else {
console.warn('CacheHttpArgsKey: value not an Observable cannot publishReplay and connect', returnValue);
}
cacheMap.set(key, returnValue);
return returnValue;
};
return descriptor;
}
Rxjs Observer / Observable + 캐싱 + 구독을 사용하여 캐시 가능한 HTTP 응답 데이터
아래 코드를 참조하십시오
* 면책 조항 : rxjs를 처음 사용하므로 관찰 / 관찰 방식을 잘못 사용하고있을 수 있습니다. 내 솔루션은 순전히 내가 찾은 다른 솔루션의 대기업이며, 잘 문서화 된 간단한 솔루션을 찾지 못한 결과입니다. 따라서 다른 사람들을 도울 수 있도록 완전한 코드 솔루션을 찾고 있습니다 (찾고 싶었던 것처럼).
*이 접근법은 GoogleFirebaseObservables를 기반으로합니다. 불행히도 나는 그들이 처한 일을 복제 할 적절한 경험 / 시간이 부족합니다. 그러나 다음은 일부 캐시 가능한 데이터에 대한 비동기 액세스를 제공하는 간단한 방법입니다.
상황 : '제품 목록'구성 요소는 제품 목록을 표시하는 작업입니다. 이 사이트는 페이지에 표시된 제품을 '필터링'하는 일부 메뉴 버튼이있는 단일 페이지 웹 앱입니다.
솔루션 : 컴포넌트가 서비스 메소드를 "구독"합니다. 서비스 메소드는 컴포넌트가 구독 콜백을 통해 액세스하는 제품 오브젝트의 배열을 리턴합니다. 서비스 메소드는 새로 작성된 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에서 로컬로 실행되는 노드 레스트 서비스 호출)에서 가져옵니다. 그런 다음 클릭하여 제품의 '필터링 된'보기로 이동하면 제품이 캐시에 있습니다.
내 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());
}
당신은 사용할 수 Cached의 장식 @ NGX 캐시 / 코어를 상기는 HTTP 호출을하는 방법에서 반환 된 값을 저장 cache storage( (가) storage구성 할 수에서 구현하시기 바랍니다 겨 씨앗 / 보편적 첫 번째 실행에 오른쪽 -). 다음에 메소드가 호출 될 때 ( 브라우저 또는 서버 플랫폼 에 상관없이 ) 값이에서 검색됩니다 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는 .
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';
}
}
다음은 클라이언트 측 및 서버 측 캐싱을위한 패키지 목록입니다.
- @ ngx-cache / core : 캐시 유틸리티
- @ ngx-cache / platform-browser : SPA / 브라우저 플랫폼 구현
- @ ngx-cache / platform-server : 서버 플랫폼 구현
- @ ngx-cache / fs-storage : 스토리지 유틸리티 (서버 플랫폼에 필요)
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이 완료되기 전에 구독을 취소하면 취소 여부가 확실하지 않습니다.
우리가 원하는 것은 이것이 여러 네트워크 요청을 일으키지 않도록하는 것입니다.
개인적으로 가장 좋아하는 것은 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.
추신 : 일반적으로 구성 요소가 파괴되면 구독을 제거하는 것이 좋습니다 . 이 답변 에서 제안 된 방법을 사용할 수 있습니다 .
좋은 답변입니다.
또는 당신은 이것을 할 수 있습니다 :
최신 버전의 rxjs에서 가져온 것입니다. 내가 사용하고 5.5.7 버전 RxJS을
import {share} from "rxjs/operators";
this.http.get('/someUrl').pipe(share());
map 후 및 subscribe 전에 share ()를 호출 하십시오 .
내 경우에는 나머지 서비스를 만들고, 데이터를 추출하고, 오류를 확인하고, 구체적 구현 서비스 (f.ex .: ContractClientService.ts)에 관찰 가능하게 반환하는 일반 서비스 (RestClientService.ts)가 있습니다. ContractComponent.ts에 observable을 반환하고,이 뷰를 구독하여 뷰를 업데이트합니다.
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;
});
}
}
캐시 클래스를 작성했습니다.
/**
* 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합니다.
이 캐시 계층을 사용하면 필요한 모든 작업을 수행하고 심지어 아약스 요청에 대한 캐시를 관리 할 수 있습니다.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
사용하기가 훨씬 쉽습니다.
@Component({
selector: 'home',
templateUrl: './html/home.component.html',
styleUrls: ['./css/home.component.css'],
})
export class HomeComponent {
constructor(AjaxService:AjaxService){
AjaxService.postCache("/api/home/articles").subscribe(values=>{console.log(values);this.articles=values;});
}
articles={1:[{data:[{title:"first",sort_text:"description"},{title:"second",sort_text:"description"}],type:"Open Source Works"}]};
}
층은 (주입 가능한 각도 서비스로서)
import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import './../rxjs/operator'
@Injectable()
export class AjaxService {
public data:Object={};
/*
private dataObservable:Observable<boolean>;
*/
private dataObserver:Array<any>=[];
private loading:Object={};
private links:Object={};
counter:number=-1;
constructor (private http: Http) {
}
private loadPostCache(link:string){
if(!this.loading[link]){
this.loading[link]=true;
this.links[link].forEach(a=>this.dataObserver[a].next(false));
this.http.get(link)
.map(this.setValue)
.catch(this.handleError).subscribe(
values => {
this.data[link] = values;
delete this.loading[link];
this.links[link].forEach(a=>this.dataObserver[a].next(false));
},
error => {
delete this.loading[link];
}
);
}
}
private setValue(res: Response) {
return res.json() || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
postCache(link:string): Observable<Object>{
return Observable.create(observer=> {
if(this.data.hasOwnProperty(link)){
observer.next(this.data[link]);
}
else{
let _observable=Observable.create(_observer=>{
this.counter=this.counter+1;
this.dataObserver[this.counter]=_observer;
this.links.hasOwnProperty(link)?this.links[link].push(this.counter):(this.links[link]=[this.counter]);
_observer.next(false);
});
this.loadPostCache(link);
_observable.subscribe(status=>{
if(status){
observer.next(this.data[link]);
}
}
);
}
});
}
}
그건 .publishReplay(1).refCount();나 .publishLast().refCount();각도 HTTP를 관찰 가능한부터 요청 후 완료합니다.
이 간단한 클래스는 결과를 캐시하므로 .value를 여러 번 구독 할 수 있으며 하나의 요청 만합니다. .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 서버에서 검색된 데이터를 관리하는 데 도움이되는 간단한 클래스 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
ngx-cacheable을 간단하게 사용할 수 있습니다 ! 시나리오에 더 적합합니다.
이것을 사용하는 이점
- 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
}
}
자세한 내용은 다음 링크를 참조하십시오.
이것이 바로 ngx-rxcache 라이브러리를 만든 것입니다.
https://github.com/adriandavidbrand/ngx-rxcache 에서 살펴보고 https://stackblitz.com/edit/angular-jxqaiv 에서 실제 예제를 확인하십시오.
이미 가지고있는 코드를 실행 해 보셨습니까?
에서 발생하는 약속에서 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
// ...
'development' 카테고리의 다른 글
| 옛날 옛적에,>가 <…보다 빠를 때, 잠깐, 뭐? (0) | 2020.03.25 |
|---|---|
| GET 요청 대신 OPTIONS 요청을받는 이유는 무엇입니까? (0) | 2020.03.25 |
| 파이썬에서 현재 모듈 내의 모든 클래스 목록을 어떻게 얻을 수 있습니까? (0) | 2020.03.25 |
| PEP8의 E128 : 시각적 들여 쓰기를 위해 밑줄이 그어진 줄은 무엇입니까? (0) | 2020.03.25 |
| IntelliJ IDEA를 사용하여 사용하지 않는 모든 코드를 찾는 방법은 무엇입니까? (0) | 2020.03.25 |