Cách chính xác để chia sẻ kết quả của một cuộc gọi mạng Angular http trong RxJs 5 là gì?


303

Bằng cách sử dụng http, chúng tôi gọi một phương thức thực hiện cuộc gọi mạng và trả về một http có thể quan sát được:

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

Nếu chúng tôi có thể quan sát điều này và thêm nhiều người đăng ký vào nó:

let network$ = getCustomer();

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

Những gì chúng tôi muốn làm, là đảm bảo rằng điều này không gây ra nhiều yêu cầu mạng.

Điều này có vẻ giống như một kịch bản bất thường, nhưng nó thực sự khá phổ biến: ví dụ: nếu người gọi đăng ký có thể quan sát để hiển thị thông báo lỗi và chuyển nó đến mẫu bằng cách sử dụng ống async, chúng tôi đã có hai thuê bao.

Cách chính xác để làm điều đó trong RxJs 5 là gì?

Cụ thể, điều này dường như hoạt động tốt:

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

Nhưng đây có phải là cách thành ngữ để làm điều này trong RxJs 5, hay chúng ta nên làm gì khác thay thế?

Lưu ý: Theo Angular 5 mới HttpClient, .map(res => res.json())phần trong tất cả các ví dụ bây giờ là vô dụng, vì kết quả JSON hiện được mặc định.


1
> chia sẻ giống hệt với xuất bản (). refCount (). Thật ra không phải vậy. Xem các cuộc thảo luận sau: github.com/ReactiveX/rxjs/issues/1363
Christian

1
câu hỏi được chỉnh sửa, theo vấn đề có vẻ như các tài liệu về mã cần được cập nhật -> github.com/ReactiveX/rxjs/blob/master/src/operator/share.ts
Đại học Angular

Tôi nghĩ rằng 'nó phụ thuộc'. Nhưng đối với các cuộc gọi mà bạn không thể lưu trữ dữ liệu cục bộ b / c thì điều đó có thể không có ý nghĩa do thay đổi / kết hợp tham số .share () dường như là điều hoàn toàn đúng. Nhưng nếu bạn có thể lưu trữ mọi thứ cục bộ một số câu trả lời khác liên quan đến ReplaySubject / BehaviorSubject cũng là giải pháp tốt.
JimB

Tôi nghĩ rằng không chỉ chúng tôi cần lưu trữ dữ liệu, chúng tôi cũng cần cập nhật / sửa đổi dữ liệu được lưu trong bộ nhớ cache. Đó là một trường hợp phổ biến. Ví dụ: nếu tôi muốn thêm một trường mới vào mô hình được lưu trong bộ nhớ cache hoặc cập nhật giá trị của trường. Có lẽ tạo một DataCacheService đơn lẻ bằng phương pháp CRUD là cách tốt hơn? Giống như cửa hàng của Redux . Bạn nghĩ sao?
slideshowp2

Bạn chỉ có thể sử dụng ngx-cacheable ! Nó phù hợp hơn với kịch bản của bạn. Tham khảo câu trả lời của tôi dưới đây
Tushar Walzade

Câu trả lời:


230

Lưu trữ dữ liệu và nếu có sẵn bộ nhớ cache, hãy trả lại yêu cầu HTTP này.

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

Ví dụ về Plunker

Bài viết này https://blog. Dùtram.io/angular/2018/03/05/advified- caching-with- rxjs.html là một lời giải thích tuyệt vời về cách lưu trữ với shareReplay.


3
do()ngược lại map()không sửa đổi sự kiện. Bạn cũng có thể sử dụng map()nhưng sau đó bạn phải đảm bảo giá trị chính xác được trả về khi kết thúc cuộc gọi lại.
Günter Zöchbauer

3
Nếu trang web cuộc gọi .subscribe()không cần giá trị bạn có thể làm điều đó bởi vì nó có thể chỉ nhận được null(tùy thuộc vào những gì this.extractDatatrả về), nhưng IMHO điều này không thể hiện rõ ý định của mã.
Günter Zöchbauer

2
Khi this.extraDatakết thúc như extraData() { if(foo) { doSomething();}}cách khác, kết quả của biểu thức cuối cùng được trả về có thể không phải là điều bạn muốn.
Günter Zöchbauer

9
@ Günter, cảm ơn bạn đã mã, nó hoạt động. Tuy nhiên, tôi đang cố gắng hiểu lý do tại sao bạn theo dõi Dữ liệu và Quan sát riêng biệt. Bạn sẽ không đạt được hiệu quả tương tự bằng cách lưu vào bộ nhớ cache chỉ có thể quan sát <Dữ liệu> như thế này chứ? if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
July.Tech

3
@HarleenKaur Đó là một lớp mà JSON nhận được được giải tuần tự hóa, để có được kiểm tra và tự động hoàn thành kiểu mạnh. Không cần sử dụng nó, nhưng nó phổ biến.
Günter Zöchbauer

44

Theo đề xuất @Cristian, đây là một cách hoạt động tốt cho các thiết bị quan sát HTTP, chỉ phát ra một lần và sau đó chúng hoàn thành:

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

Có một vài vấn đề khi sử dụng phương pháp này - có thể hủy hoặc trả lại có thể quan sát được. Điều này có thể không phải là một vấn đề cho bạn, nhưng sau đó nó có thể. Nếu đây là một vấn đề thì sharenhà điều hành có thể là một lựa chọn hợp lý (mặc dù với một số trường hợp cạnh khó chịu). Để thảo luận sâu về các tùy chọn, hãy xem phần bình luận trong bài đăng trên blog này: blog.jhades.org/iêu
Christian

1
Làm rõ nhỏ ... Mặc dù publishLast().refCount()không thể hủy bỏ nguồn được chia sẻ mà không thể hủy bỏ, nhưng một khi tất cả các đăng ký cho trả lại có thể quan sát được refCountđã bị hủy, hiệu ứng ròng là nguồn có thể quan sát được sẽ bị hủy đăng ký, hủy bỏ nếu nó ở chế độ "inflight"
Christian

@Christian Này, bạn có thể giải thích ý của bạn bằng cách nói "không thể hủy bỏ hoặc thử lại" không? Cảm ơn.
không xác định

37

CẬP NHẬT: Ben Lesh cho biết bản phát hành nhỏ tiếp theo sau 5.2.0, bạn sẽ có thể chỉ cần gọi shareReplay () để thực sự lưu trữ bộ đệm.

TRƯỚC KHI .....

Đầu tiên, không sử dụng share () hoặc PublishReplay (1) .refCount (), chúng giống nhau và vấn đề với nó, là nó chỉ chia sẻ nếu các kết nối được thực hiện trong khi có thể quan sát được kích hoạt, nếu bạn kết nối sau khi hoàn thành , nó tạo ra một bản dịch mới có thể quan sát được một lần nữa, bản dịch, không thực sự lưu vào bộ đệm.

Birowski đã đưa ra giải pháp đúng ở trên, đó là sử dụng ReplaySubject. ReplaySubject sẽ lưu trữ các giá trị bạn cung cấp cho nó (bufferSize) trong trường hợp của chúng tôi 1. Nó sẽ không tạo ra một chia sẻ mới có thể quan sát được như share () khi refCount đạt đến 0 và bạn tạo một kết nối mới, đó là hành vi phù hợp để lưu vào bộ đệm.

Đây là một chức năng có thể tái sử dụng

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

Đây là cách sử dụng nó

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

Dưới đây là phiên bản nâng cao hơn của chức năng có thể lưu trong bộ nhớ cache Cái này cho phép có bảng tra cứu riêng + khả năng cung cấp bảng tra cứu tùy chỉnh. Bằng cách này, bạn không phải kiểm tra this._cache như trong ví dụ trên. Cũng lưu ý rằng thay vì chuyển đối số có thể quan sát thành đối số đầu tiên, bạn chuyển một hàm trả về các vật thể quan sát được, điều này là do các Angular's thực thi ngay lập tức, vì vậy bằng cách trả về một hàm thực thi lười biếng, chúng ta có thể quyết định không gọi nó nếu nó đã ở trong bộ nhớ cache của chúng tôi.

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

Sử dụng:

getData() => cacheable(this._http.get("YOUR URL"), "this is key for my cache")

Có bất kỳ lý do để không sử dụng giải pháp này như là một toán tử RxJs : const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();? Vì vậy, nó hoạt động giống như bất kỳ nhà điều hành nào khác ..
Felix

31

rxjs 5.4.0 có phương thức shareReplay mới .

Tác giả nói rõ ràng "lý tưởng để xử lý những thứ như lưu trữ kết quả AJAX"

rxjs PR # 2443 feat (shareReplay): thêm shareReplaybiến thể củapublishReplay

shareReplay trả về một thứ có thể quan sát được là nguồn được phát đa hướng qua ReplaySubject. Chủ đề phát lại đó được tái chế do lỗi từ nguồn, nhưng không hoàn thành nguồn. Điều này làm cho shareReplay lý tưởng để xử lý những thứ như lưu trữ kết quả AJAX, vì nó có thể thử lại được. Tuy nhiên, hành vi lặp lại của nó khác với chia sẻ ở chỗ nó sẽ không lặp lại nguồn có thể quan sát được, thay vào đó nó sẽ lặp lại các giá trị có thể quan sát được của nguồn.


Có liên quan đến điều này? Những tài liệu này là từ năm 2014 mặc dù. github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/ mẹo
Aaron Hoffman

4
Tôi đã thử thêm .shareReplay (1, 10000) để có thể quan sát được nhưng tôi không nhận thấy bất kỳ thay đổi bộ nhớ cache hoặc hành vi nào. Có một ví dụ làm việc có sẵn?
Aydus-Matthew

Nhìn vào changelog github.com/ReactiveX/rxjs/blob/, Nó xuất hiện trước đó, đã bị xóa trong v5, được thêm lại vào 5.4 - liên kết sách rx đó có đề cập đến v4, nhưng nó tồn tại trong LTS v5.5.6 hiện tại và đó là trong v6. Tôi tưởng tượng liên kết sách rx đã hết hạn.
Jason Awbrey

25

theo bài viết này

Hóa ra chúng ta có thể dễ dàng thêm bộ nhớ đệm vào có thể quan sát bằng cách thêm PublishReplay (1) và refCount.

Vì vậy, bên trong nếu báo cáo chỉ nối

.publishReplay(1)
.refCount();

đến .map(...)


11

phiên bản rxjs 5.4.0 (2017-05-09) thêm hỗ trợ cho shareReplay .

Tại sao nên sử dụng shareReplay?

Bạn thường muốn sử dụng shareReplay khi bạn có các tính toán phụ hoặc tính thuế mà bạn không muốn được thực thi giữa nhiều người đăng ký. Nó cũng có thể có giá trị trong các tình huống mà bạn biết bạn sẽ có những người đăng ký muộn vào một luồng cần truy cập vào các giá trị được phát ra trước đó. Khả năng này phát lại các giá trị khi đăng ký là những gì phân biệt chia sẻ và chia sẻ.

Bạn có thể dễ dàng sửa đổi một dịch vụ góc để sử dụng dịch vụ này và trả lại kết quả có thể quan sát được với kết quả được lưu trong bộ nhớ cache sẽ chỉ thực hiện cuộc gọi http một lần duy nhất (giả sử cuộc gọi đầu tiên là thành công).

Ví dụ dịch vụ góc

Đây là một dịch vụ khách hàng rất đơn giản mà sử dụng shareReplay.

khách hàng

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

Lưu ý rằng việc gán trong hàm tạo có thể được chuyển sang phương thức getCustomersnhưng vì các vật thể quan sát được trả về HttpClientlà "lạnh" nên việc này trong hàm tạo được chấp nhận vì lệnh gọi http sẽ chỉ được thực hiện với lệnh gọi đầu tiên subscribe.

Ngoài ra, giả định ở đây là dữ liệu trả về ban đầu không bị cũ trong thời gian tồn tại của ứng dụng.


Tôi thực sự thích mẫu này và đang tìm cách triển khai nó trong một thư viện chia sẻ các dịch vụ api mà tôi sử dụng trên một số ứng dụng. Một ví dụ là Dịch vụ người dùng và ở mọi nơi ngoại trừ một vài nơi không cần phải vô hiệu hóa bộ đệm trong suốt thời gian sử dụng ứng dụng, nhưng đối với những trường hợp đó, tôi sẽ làm thế nào để vô hiệu hóa nó mà không khiến đăng ký trước trở thành mồ côi?
SirTophamHatt

10

Tôi đánh dấu sao câu hỏi, nhưng tôi sẽ thử và giải quyết vấn đề này.

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

Đây là bằng chứng :)

Chỉ có một takeaway: getCustomer().subscribe(customer$)

Chúng tôi không đăng ký phản hồi api của getCustomer(), chúng tôi đang đăng ký ReplaySubject có thể quan sát được, nó cũng có thể đăng ký một Observable khác và (điều này rất quan trọng) giữ giá trị được phát ra cuối cùng của nó và tái xuất bản nó cho bất kỳ của nó (ReplaySubject ) thuê bao.


1
Tôi thích cách tiếp cận này vì nó sử dụng rxjs tốt và không cần thêm logic tùy chỉnh, cảm ơn bạn
Thibs

7

Tôi đã tìm thấy một cách để lưu trữ kết quả http vào sessionStorage và sử dụng nó cho phiên này, để nó sẽ không bao giờ gọi lại máy chủ nữa.

Tôi đã sử dụng nó để gọi API github để tránh giới hạn sử dụng.

@Injectable()
export class HttpCache {
  constructor(private http: Http) {}

  get(url: string): Observable<any> {
    let cached: any;
    if (cached === sessionStorage.getItem(url)) {
      return Observable.of(JSON.parse(cached));
    } else {
      return this.http.get(url)
        .map(resp => {
          sessionStorage.setItem(url, resp.text());
          return resp.json();
        });
    }
  }
}

FYI, sessionStorage giới hạn là 5M (hoặc 4,75M). Vì vậy, nó không nên được sử dụng như thế này cho tập dữ liệu lớn.

------ chỉnh sửa -------------
Nếu bạn muốn làm mới dữ liệu bằng F5, sử dụng dữ liệu hiện đại thay vì sessionStorage;

@Injectable()
export class HttpCache {
  cached: any = {};  // this will store data
  constructor(private http: Http) {}

  get(url: string): Observable<any> {
    if (this.cached[url]) {
      return Observable.of(this.cached[url]));
    } else {
      return this.http.get(url)
        .map(resp => {
          this.cached[url] = resp.text();
          return resp.json();
        });
    }
  }
}

Nếu bạn sẽ lưu trữ trong Lưu trữ phiên thì bạn sẽ đảm bảo rằng Lưu trữ phiên bị hủy khi bạn rời khỏi ứng dụng?
Gags

nhưng điều này giới thiệu hành vi bất ngờ cho người dùng. Khi người dùng nhấn F5 hoặc nút refresh của trình duyệt, sau đó anh ta mong đợi dữ liệu mới từ máy chủ. Nhưng thực sự anh ta đang nhận được dữ liệu lỗi thời từ localStorage. Báo cáo lỗi, vé hỗ trợ, vv đến ... Như tên đã sessionStoragenói, tôi sẽ chỉ sử dụng nó cho dữ liệu dự kiến ​​sẽ phù hợp cho toàn bộ phiên.
Martin Schneider

@ MA-Maddin như tôi đã nói "Tôi đã sử dụng nó để tránh giới hạn sử dụng". Nếu bạn muốn dữ liệu được làm mới bằng F5, bạn cần sử dụng bộ nhớ thay vì sessionStorage. Câu trả lời đã được chỉnh sửa với phương pháp này.
allenhwkim

vâng, đó có thể là một trường hợp sử dụng. Tôi vừa bị kích hoạt vì mọi người đang nói về Cache và OP có getCustomertrong ví dụ của anh ấy. ;) Vì vậy, chỉ muốn cảnh báo một số ppl có thể không nhìn thấy rủi ro :)
Martin Schneider

5

Việc triển khai bạn chọn sẽ phụ thuộc vào việc bạn có muốn hủy đăng ký () để hủy yêu cầu HTTP hay không.

Trong mọi trường hợp, trình trang trí TypeScript là một cách hay để chuẩn hóa hành vi. Đây là bài tôi đã viết:

  @CacheObservableArgsKey
  getMyThing(id: string): Observable<any> {
    return this.http.get('things/'+id);
  }

Định nghĩa trang trí:

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

Xin chào @Arlo - ví dụ trên không biên dịch. Property 'connect' does not exist on type '{}'.từ dòng returnValue.connect();. Bạn có thể xây dựng?
Hoof

4

Dữ liệu phản hồi HTTP có thể lưu bằng cách sử dụng Rxjs Observer / Observable + Cache

Xem mã dưới đây

* từ chối trách nhiệm: Tôi chưa quen với rxjs, vì vậy hãy nhớ rằng tôi có thể đang lạm dụng phương pháp quan sát / quan sát viên. Giải pháp của tôi hoàn toàn là một tập hợp các giải pháp khác mà tôi tìm thấy, và là hậu quả của việc không tìm được một giải pháp tài liệu đơn giản. Vì vậy, tôi đang cung cấp giải pháp mã hoàn chỉnh của mình (như tôi muốn tìm thấy) với hy vọng rằng nó sẽ giúp được người khác.

* lưu ý, cách tiếp cận này dựa trên GoogleFirebaseObservables. Thật không may, tôi thiếu kinh nghiệm / thời gian thích hợp để sao chép những gì họ đã làm dưới mui xe. Nhưng sau đây là một cách đơn giản để cung cấp quyền truy cập không đồng bộ vào một số dữ liệu có thể lưu vào bộ đệm.

Tình huống : Thành phần 'danh sách sản phẩm' được giao nhiệm vụ hiển thị danh sách các sản phẩm. Trang web này là một ứng dụng web một trang với một số nút menu sẽ 'lọc' các sản phẩm được hiển thị trên trang.

Giải pháp : Thành phần "đăng ký" một phương thức dịch vụ. Phương thức dịch vụ trả về một mảng các đối tượng sản phẩm mà thành phần truy cập thông qua cuộc gọi lại đăng ký. Phương thức dịch vụ kết thúc hoạt động của nó trong một Người quan sát mới được tạo và trả về người quan sát. Bên trong người quan sát này, nó tìm kiếm dữ liệu được lưu trong bộ nhớ cache và chuyển nó trở lại thuê bao (thành phần) và trả về. Mặt khác, nó đưa ra một cuộc gọi http để lấy dữ liệu, đăng ký phản hồi, nơi bạn có thể xử lý dữ liệu đó (ví dụ: ánh xạ dữ liệu vào mô hình của riêng bạn) và sau đó chuyển dữ liệu trở lại cho thuê bao.

Mật mã

sản phẩm-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;
    });
  }
}

sản phẩm dịch vụ.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$;
  }
}

sản phẩm.ts (mô hình)

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

Đây là một mẫu đầu ra mà tôi thấy khi tôi tải trang trong Chrome. Lưu ý rằng trên tải ban đầu, các sản phẩm được tìm nạp từ http (gọi đến dịch vụ nghỉ ngơi nút của tôi, đang chạy cục bộ trên cổng 3000). Sau đó, khi tôi nhấp để điều hướng đến chế độ xem 'được lọc' của các sản phẩm, các sản phẩm được tìm thấy trong bộ đệm.

Nhật ký Chrome của tôi (bảng điều khiển):

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

... [nhấp vào nút menu để lọc sản phẩm] ...

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

Kết luận: Đây là cách đơn giản nhất mà tôi đã tìm thấy (cho đến nay) để triển khai dữ liệu phản hồi http có thể lưu trong bộ nhớ cache. Trong ứng dụng góc cạnh của tôi, mỗi lần tôi điều hướng đến một chế độ xem khác nhau của các sản phẩm, thành phần danh sách sản phẩm sẽ tải lại. ProductService dường như là một trường hợp được chia sẻ, do đó, bộ đệm cục bộ của 'sản phẩm: Sản phẩm []' trong ProductService được giữ lại trong khi điều hướng và các cuộc gọi tiếp theo đến "Get Products ()" trả về giá trị được lưu trong bộ nhớ cache. Một lưu ý cuối cùng, tôi đã đọc các bình luận về cách đóng cửa quan sát / đăng ký khi bạn kết thúc để ngăn chặn 'rò rỉ bộ nhớ'. Tôi đã không bao gồm điều này ở đây, nhưng đó là điều cần lưu ý.


1
Lưu ý - Kể từ đó tôi đã tìm thấy một giải pháp mạnh mẽ hơn, liên quan đến RxJS BehaviorSubjects, giúp đơn giản hóa mã và cắt giảm đáng kể 'chi phí hoạt động'. Trong sản phẩm.service.ts, 1. nhập {BehaviorSubject} từ 'rxjs'; 2. thay đổi 'sản phẩm: Sản phẩm []' thành 'sản phẩm $: BehaviorSubject <Product []> = new BehaviorSubject <Product []> ([]);' 3. Bây giờ bạn có thể chỉ cần gọi http mà không cần trả lại bất cứ điều gì. http_get Products () {this.http.get (...). map (res => res.json ()). đăng ký (sản phẩm => this.product $ .next (sản phẩm))};
ObjectiveTC

1
Biến cục bộ 'sản phẩm $' là một behaviorSubject, cả EMIT và STORE các sản phẩm mới nhất (từ sản phẩm $ .next (..) gọi trong phần 3). Bây giờ trong các thành phần của bạn, tiêm dịch vụ như bình thường. Bạn nhận được giá trị được gán gần đây nhất của sản phẩm $ bằng cách sử dụng sản phẩmService.product $ .value. Hoặc đăng ký sản phẩm $ nếu bạn muốn thực hiện một hành động bất cứ khi nào sản phẩm $ nhận được một giá trị mới (nghĩa là hàm sản phẩm $ .next (...) được gọi trong phần 3).
ObjectiveTC

1
Ví dụ: trong sản phẩm.component.ts ... this.productService.product $ .takeUntil (this.ngUnsubscribe) .subscribe ((sản phẩm) => {this.c Category); hãy lọcSản phẩm = this.productService.get ProductsByC Category (this.c Category); this .products = filtered Products; });
ObjectiveTC

1
Một lưu ý quan trọng về việc hủy đăng ký khỏi các đài quan sát: ".takeUntil (this.ngUnsubscribe)". Xem câu hỏi / câu trả lời về ngăn xếp này, dường như hiển thị cách được đề xuất 'de-facto' để hủy đăng ký khỏi các sự kiện: stackoverflow.com/questions
ObjectiveTC

1
Thay thế là .first () hoặc .take (1) nếu có thể quan sát được chỉ để nhận dữ liệu một lần. Tất cả các 'luồng vô hạn' khác có thể được hủy đăng ký trong 'ngOnDestroy ()' và nếu bạn không thì bạn có thể kết thúc bằng các cuộc gọi lại 'có thể quan sát' trùng lặp. stackoverflow.com/questions/28007777/ từ
ObjectiveTC

3

Tôi giả sử rằng @ ngx-cache / core có thể hữu ích để duy trì các tính năng bộ đệm cho các cuộc gọi http, đặc biệt nếu cuộc gọi HTTP được thực hiện cả trên trình duyệtmáy chủ nền tảng .

Giả sử chúng ta có phương pháp sau:

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

Bạn có thể sử dụng Cachedtrang trí của @ NGX-cache / lõi để lưu trữ các giá trị trả về từ phương pháp thực hiện cuộc gọi HTTP tại cache storage( các storagethể được cấu hình, hãy kiểm tra việc thực hiện tại ng hạt / phổ thông ) - ngay trên việc thực hiện đầu tiên. Lần tiếp theo phương thức được gọi (bất kể trên nền tảng trình duyệt hoặc máy chủ ), giá trị được lấy từ 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());
}

Ngoài ra còn có khả năng sử dụng các phương pháp bộ nhớ đệm ( has, get, set) bằng cách sử dụng API bộ nhớ đệm .

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

Dưới đây là danh sách các gói, cả cho bộ đệm ẩn phía máy khách và phía máy chủ:


1

rxjs 5.3.0

Tôi chưa hài lòng với .map(myFunction).publishReplay(1).refCount()

Với nhiều người đăng ký, .map()thực thi myFunctionhai lần trong một số trường hợp (tôi hy vọng nó chỉ thực hiện một lần). Một sửa chữa dường như làpublishReplay(1).refCount().take(1)

Một điều khác bạn có thể làm, chỉ là không sử dụng refCount()và làm cho Observable nóng ngay lập tức:

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

Điều này sẽ bắt đầu yêu cầu HTTP bất kể người đăng ký. Tôi không chắc chắn nếu hủy đăng ký trước khi kết thúc HTTP GET sẽ hủy hay không.


1

Những gì chúng tôi muốn làm, là đảm bảo rằng điều này không gây ra nhiều yêu cầu mạng.

Sở thích cá nhân của tôi là sử dụng các asyncphương thức cho các cuộc gọi thực hiện yêu cầu mạng. Bản thân các phương thức không trả về một giá trị, thay vào đó chúng cập nhật một BehaviorSubjectdịch vụ trong cùng một dịch vụ mà các thành phần sẽ đăng ký.

Bây giờ Tại sao sử dụng BehaviorSubjectthay vì một Observable? Bởi vì,

  • Khi đăng ký BehaviorSubject trả về giá trị cuối cùng trong khi A có thể quan sát thường xuyên chỉ kích hoạt khi nhận được onnext.
  • Nếu bạn muốn lấy giá trị cuối cùng của BehaviorSubject trong một mã không thể quan sát được (không có đăng ký), bạn có thể sử dụng getValue()phương thức này.

Thí dụ:

khách hàng

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

Sau đó, bất cứ nơi nào cần thiết, chúng tôi chỉ có thể đăng ký customers$.

public ngOnInit(): void {
    this.customerService.customers$
    .subscribe((customers: Customer[]) => this.customerList = customers);
}

Hoặc có thể bạn muốn sử dụng nó trực tiếp trong một mẫu

<li *ngFor="let customer of customerService.customers$ | async"> ... </li>

Vì vậy, bây giờ, cho đến khi bạn thực hiện một cuộc gọi khác getCustomers, dữ liệu được giữ lại trong customers$BehaviorSubject.

Vậy nếu bạn muốn làm mới dữ liệu này thì sao? chỉ cần gọi chogetCustomers()

public async refresh(): Promise<void> {
    try {
      await this.customerService.getCustomers();
    } 
    catch (e) {
      // request failed, handle exception
      console.error(e);
    }
}

Sử dụng phương pháp này, chúng tôi không phải giữ lại dữ liệu giữa các cuộc gọi mạng tiếp theo khi được xử lý bởi BehaviorSubject.

PS: Thông thường khi một thành phần bị phá hủy, đó là một cách tốt để loại bỏ các đăng ký, vì bạn có thể sử dụng phương pháp được đề xuất trong câu trả lời này .


1

Câu trả lời tuyệt vời.

Hoặc bạn có thể làm điều này:

Đây là từ phiên bản mới nhất của rxjs. Tôi đang sử dụng phiên bản 5.5.7 của RxJS

import {share} from "rxjs/operators";

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

0

Chỉ cần gọi share () sau bản đồ và trước khi đăng ký .

Trong trường hợp của tôi, tôi có một dịch vụ chung (RestClientService.ts) đang thực hiện cuộc gọi còn lại, trích xuất dữ liệu, kiểm tra lỗi và trả lại có thể quan sát được cho một dịch vụ triển khai cụ thể (ví dụ: ContractClientService.ts), cuối cùng là triển khai cụ thể này trả về có thể quan sát cho de ContractComponent.ts và điều này đăng ký để cập nhật chế độ xem.

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

Tôi đã viết một lớp bộ đệm,

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

}

Tất cả đều tĩnh vì cách chúng tôi sử dụng nó, nhưng hãy thoải mái biến nó thành một lớp học bình thường và một dịch vụ. Tôi không chắc chắn nếu angular giữ một trường hợp duy nhất trong toàn bộ thời gian (mới đối với Angular2).

Và đây là cách tôi sử dụng nó:

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

Tôi cho rằng có thể có một cách thông minh hơn, sẽ sử dụng một số Observablethủ thuật nhưng điều này chỉ tốt cho mục đích của tôi.


0

Chỉ cần sử dụng lớp bộ đệm này, nó thực hiện mọi thứ bạn yêu cầu và thậm chí quản lý bộ đệm cho các yêu cầu ajax.

http://www.ravinderpayal.com/bloss/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html

Nó rất dễ sử dụng

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

Lớp (như một dịch vụ góc có thể tiêm) là

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

Đó là .publishReplay(1).refCount();hoặc .publishLast().refCount();kể từ khi Angular http có thể quan sát hoàn thành sau khi yêu cầu.

Lớp đơn giản này lưu trữ kết quả để bạn có thể đăng ký .value nhiều lần và chỉ thực hiện 1 yêu cầu. Bạn cũng có thể sử dụng .reload () để thực hiện yêu cầu mới và xuất bản dữ liệu.

Bạn có thể sử dụng nó như:

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

và nguồn:

export class RestResource {

  static readonly LOADING: string = 'RestResource_Loading';
  static readonly ERROR: string = 'RestResource_Error';
  static readonly IDLE: string = 'RestResource_Idle';

  public value: Observable<any>;
  public status: Observable<string>;
  private loadStatus: Observer<any>;

  private reloader: Observable<any>;
  private reloadTrigger: Observer<any>;

  constructor(requestObservableFn: () => Observable<any>) {
    this.status = Observable.create((o) => {
      this.loadStatus = o;
    });

    this.reloader = Observable.create((o: Observer<any>) => {
      this.reloadTrigger = o;
    });

    this.value = this.reloader.startWith(null).switchMap(() => {
      if (this.loadStatus) {
        this.loadStatus.next(RestResource.LOADING);
      }
      return requestObservableFn()
        .map((res) => {
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.IDLE);
          }
          return res;
        }).catch((err)=>{
          if (this.loadStatus) {
            this.loadStatus.next(RestResource.ERROR);
          }
          return Observable.of(null);
        });
    }).publishReplay(1).refCount();
  }

  reload() {
    this.reloadTrigger.next(null);
  }

}

0

Bạn có thể xây dựng lớp đơn giản Bộ nhớ đệm <> giúp quản lý dữ liệu được truy xuất từ ​​máy chủ http có nhiều người đăng ký:

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

}

Sử dụng

Khai báo đối tượng có thể lưu <> (có lẽ là một phần của dịch vụ):

list: Cacheable<string> = new Cacheable<string>();

và xử lý:

this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string[]);
}

Gọi từ một thành phần:

//gets data from server
List.getData().subscribe(…)

Bạn có thể có một số thành phần đăng ký nó.

Thông tin chi tiết và ví dụ mã có tại đây: http://devinstance.net/articles/20171021/rxjs-cachizable


0

Bạn chỉ có thể sử dụng ngx-cacheable ! Nó phù hợp hơn với kịch bản của bạn.

Lợi ích của việc sử dụng này

  • Nó chỉ gọi API còn lại một lần, lưu lại phản hồi và trả lại tương tự cho các yêu cầu sau.
  • Có thể gọi API theo yêu cầu sau khi tạo / cập nhật / xóa hoạt động.

Vì vậy, lớp dịch vụ của bạn sẽ giống như thế này -

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

Đây là đường dẫn để tham khảo thêm.


-4

Bạn đã thử chạy mã bạn đã có?

Vì bạn đang xây dựng Đài quan sát từ lời hứa getJSON(), nên yêu cầu mạng được thực hiện trước khi bất kỳ ai đăng ký. Và lời hứa kết quả được chia sẻ bởi tất cả các thuê bao.

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
// ...

Tôi đã chỉnh sửa câu hỏi để biến nó thành Angular 2 cụ thể
Đại học Angular
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.