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





9 Answers

@Cristian 제안에 따르면, 이것은 한 번만 방출 한 다음 완료하는 HTTP 관찰 가능 항목에 대해 잘 작동하는 한 가지 방법입니다.

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

Http를 사용하여 네트워크 호출을 수행하고 http observable을 반환하는 메소드를 호출합니다.

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에서 이것을하는 관용적 인 방법입니까, 아니면 대신 다른 것을해야합니까?

참고 : 앵귤러 5 새 HttpClient 당, 모든 예제에서 .map(res => res.json()) 부분은 이제 쓸모가 없습니다. JSON 결과는 기본적으로 가정됩니다.




article 에 따르면

publishReplay (1) 및 refCount를 추가하여 관찰 가능 항목에 캐싱을 쉽게 추가 할 수 있습니다.

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

여기에 proof :)

하나의 테이크 어웨이가 있습니다 : getCustomer().subscribe(customer$)

우리는 getCustomer() 의 api 응답을 구독하지 않습니다. 우리는 또 다른 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();
        });
    }
  }
}

참고로 세션 저장 용량은 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 호출이 브라우저서버 플랫폼 모두에서 이루어진 경우 특히 @ngx-cache/core 가 http 호출에 대한 캐싱 기능을 유지하는 데 유용 할 수 있다고 가정합니다.

다음과 같은 방법이 있다고 가정 해 보겠습니다.

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

@ngx-cache/coreCached 데코레이터를 사용하여 Cached 저장소에서 HTTP 호출을 만드는 메소드의 반환 값을 저장할 수 있습니다 ( 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());
}

캐싱 API 를 사용하여 캐싱 메소드 ( has , get , set )를 사용할 수도 있습니다.

anyclass.ts

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

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

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

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

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




우리가하고 싶은 일은 이것이 다중 네트워크 요청을 야기하지 않는지 확인하는 것입니다.

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

이제 Observable 대신 BehaviorSubject 를 사용하는 이유는 무엇입니까? 때문에,

  • 구독시 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 가 처리 할 때 후속 네트워크 호출간에 데이터를 명시 적으로 유지할 필요가 없습니다.

추신 : 일반적으로 구성 요소가 파괴되면 구독을 없애는 것이 좋습니다. this 답변에서 제안하는 방법을 사용할 수 있습니다.




나는 캐시 클래스를 썼다.

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

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

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

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

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

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

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

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

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

}

그것은 우리가 그것을 사용하는 방법 때문에 모든 정적이지만, 그것을 정상적인 클래스와 서비스로 만들 수 있습니다. 앵귤러가 전체 인스턴스에 대해 단일 인스턴스를 유지하는지 확신 할 수 없습니다 (Angular2에 새로 추가됨).

그리고 이것이 내가 그것을 사용하는 방법입니다.

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

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

나는 좀 더 영리한 방법이있을 수 있다고 생각하지만, 약간의 Observable트릭을 사용 하겠지만, 이것은 내 목적을 위해서는 괜찮았다.




그것은의 .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);
  }

}



좋은 대답.

아니면 이렇게 할 수 있습니다 :

이것은 최신 버전의 rxjs입니다. 내가 사용하고 5.5.7 버전 RxJS을

import {share} from "rxjs/operators";

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



Related