2015-12-05 129 views
53

我通過服務公開HTTP GET請求,並且有幾個組件正在使用此數據(用戶的配置文件詳細信息)。我希望第一個組件請求能夠實際向服務器執行HTTP GET請求並緩存結果,以便隨後的請求將使用緩存的數據,而不是再次調用服務器。使用angular2 http服務緩存結果

這是一個服務的例子,你會如何推薦使用Angular2和typescript來實現這個緩存層。

import {Inject, Injectable} from 'angular2/core'; 
import {Http, Headers} from "angular2/http"; 
import {JsonHeaders} from "./BaseHeaders"; 
import {ProfileDetails} from "../models/profileDetails"; 

@Injectable() 
export class ProfileService{ 
    myProfileDetails: ProfileDetails = null; 

    constructor(private http:Http) { 

    } 

    getUserProfile(userId:number) { 
     return this.http.get('/users/' + userId + '/profile/', { 
       headers: headers 
      }) 
      .map(response => { 
       if(response.status==400) { 
        return "FAILURE"; 
       } else if(response.status == 200) { 
        this.myProfileDetails = new ProfileDetails(response.json()); 
        return this.myProfileDetails; 
       } 
      }); 
    } 
} 
+0

另一個有趣的解決方案,我認爲你正在尋找[分享](https://github.com/Reactive-Extensions/RxJS/blob/master /doc/api/core/operators/share.md)。我有一個[plnkr](http://plnkr.co/edit/hM4TSt4hlx4DA4xe37WU?p=preview),所以你可以看到它的工作。請注意,這不是緩存,但它可能適用於你:)(運行一次,看到網絡選項卡,然後從'http.get'中移除'.share()'並查看其差異)。 –

+0

我試過你的方法,但是當從兩個不同的組件調用getUserProfile(帶.share())時,GET請求仍然會在服務器上執行兩次。雖然ProfileService是使用@Inject(ProfileService)profileService在調用組件的兩個構造函數中注入的。我在這裏錯過了什麼? – Sagi

+0

,這取決於。如果你在每個組件中注入服務,你會得到兩個不同的實例(通過注入我的意思是使用'providers/viewProviers')。如果是這種情況,應該只在頂層組件中注入它(在這兩者之間)。如果情況並非如此,如果可能的話,你應該添加更多的代碼和repro。 –

回答

10

關於你的最後一個註釋,這是我能想到的最簡單的方法:創建將有一個屬性和屬性將保持請求的服務。

class Service { 
    _data; 
    get data() { 
    return this._data; 
    } 
    set data(value) { 
    this._data = value; 
    } 
} 

就這麼簡單。 plnkr中的其他一切都將保持不變。我從服務中刪除了請求,因爲它會自動實例化(我們沒有做new Service...,我不知道通過構造函數傳遞參數的簡單方法)。

所以,現在,我們有服務,我們現在做的是使該請求在我們的組件,並將其分配給服務變量data

class App { 
    constructor(http: Http, svc: Service) { 

    // Some dynamic id 
    let someDynamicId = 2; 

    // Use the dynamic id in the request 
    svc.data = http.get('http://someUrl/someId/'+someDynamicId).share(); 

    // Subscribe to the result 
    svc.data.subscribe((result) => { 
     /* Do something with the result */ 
    }); 
    } 
} 

請記住,我們的服務實例是同一個,每組件,所以當我們爲data分配一個值時,它會反映在每個組件中。

這是plnkr與一個工作示例。

參考

+0

嗨,很好的例子,但它不起作用,例如,如果你有點擊事件的請求,它會每次都會產生一個新的xhr請求例如:http://plnkr.co/edit/Z8amRJmxQ70z9ltBALbk?p=preview (點擊藍色方塊並觀察網絡標籤)。 在我的應用程序中,我創建了一個新的ReplaySubject Observable來緩存HTTP,我想使用share()方法,但它很奇怪,爲什麼在某些情況下它不起作用。 – tibbus

+0

啊,實際上我得到它(經過一些測試並閱讀rxjs文檔),所以它將共享相同的可觀察值到所有現有訂閱,但是一旦沒有訂閱並且您創建一個新訂閱,那麼它會請求一個新的價值,因此一個新的xhr請求。 – tibbus

+0

@Eric Martinez:搶劫者不再運行了... –

69

share()運營工作只是在第一次請求,當所有的訂閱服務與您共創一個又一個的話,將無法正常工作,它會做另一個請求。 (這種情況是很常見的,作爲angular2 SPA你總是創建/銷燬組件)

我用ReplaySubject存儲從HTTP觀察到的值。可觀測值可以爲其訂戶提供以前的價值。

的服務:

@Injectable() 
export class DataService { 
    private dataObs$ = new ReplaySubject(1); 

    constructor(private http: HttpClient) { } 

    getData(forceRefresh?: boolean) { 
     // If the Subject was NOT subscribed before OR if forceRefresh is requested 
     if (!this.dataObs$.observers.length || forceRefresh) { 
      this.http.get('http://jsonplaceholder.typicode.com/posts/2').subscribe(
       data => this.dataObs$.next(data), 
       error => { 
        this.dataObs$.error(error); 
        // Recreate the Observable as after Error we cannot emit data anymore 
        this.dataObs$ = new ReplaySubject(1); 
       } 
      ); 
     } 

     return this.dataObs$; 
    } 
} 

組件:

@Component({ 
    selector: 'my-app', 
    template: `<div (click)="getData()">getData from AppComponent</div>` 
}) 
export class AppComponent { 
    constructor(private dataService: DataService) {} 

getData() { 
    this.dataService.getData().subscribe(
     requestData => { 
      console.log('ChildComponent', requestData); 
     }, 
     // handle the error, otherwise will break the Observable 
     error => console.log(error) 
    ); 
} 
    } 
} 

fully working PLUNKER
(觀察控制檯和網絡選項卡)

+0

Gunter的回答非常有幫助,但由於它使用了純粹的RxJS功能,所以你的看法似乎更好。一旦你知道要使用什麼(ReplaySubject!),它是多麼的美好和簡單。謝謝! :) – LittleTiger

+0

謝謝,我很高興你喜歡它。我也編輯了這篇文章,並討論了另外兩個案例,這些案例沒有涉及:請求失敗的情況下,現在它會提出另一個請求,並在同一時間發出很多請求。 – tibbus

+0

這是這個問題的最佳答案:) –

32

我省略了userId的處理。它需要管理一組data和一組observable(每個請求userId)。

import {Injectable} from '@angular/core'; 
import {Http, Headers} from '@angular/http'; 
import {Observable} from 'rxjs/Observable'; 
import 'rxjs/observable/of'; 
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; 
    } 
    } 
} 

Plunker example

你可以找到https://stackoverflow.com/a/36296015/217408

+1

完美的完整示例,即使使用this.observable檢查。 .share()非常重要,一開始就不知道要查找什麼,不容易弄清楚。Observable.of()就是我個人正在尋找的東西。現在只需添加一點檢查,以便在數據超過一定時間時重複請求:) – LittleTiger

+0

@Günter:你能分享一下你的plunker代碼嗎? :) –

+0

@pdfarhad好主意:)更新了答案。代碼包含了一些現在應該修復的錯誤。 –