2015-12-05 2 views
53

Я предоставляю HTTP-запрос GET через службу, а несколько компонентов используют эти данные (данные профиля у пользователя). Я хотел бы, чтобы первый запрос компонента фактически выполнял HTTP-запрос GET на сервер и кэшировал результаты, поэтому последующие запросы будут использовать кэшированные данные, а не снова звонить на сервер.результаты кэширования с помощью angular2 http service

Вот пример службы, как бы вы рекомендовали реализовать этот уровень кэша с помощью Angular2 и машинописного текста.

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), чтобы вы могли видеть его работу. Обратите внимание, что это не кеширование, но оно может работать для вас :) (запустите его один раз и просмотрите вкладку сети, а затем удалите '.share()' из 'http.get' и увидите разницу). –

+0

Я пробовал ваш метод, но при вызове getUserProfile (с .share()) из двух разных компонентов запрос GET по-прежнему выполняется дважды на сервере. Это связано с тем, что ProfileService вводится в обоих конструкторах вызывающих компонентов, используя профиль @Inject (ProfileService) profileService. Что мне здесь не хватает? – Sagi

+0

это зависит. Если вы вводите услугу в каждом компоненте, вы получаете два разных экземпляра (путем инъекции я имею в виду использование «providers/viewProviers»). Если это так, вы должны ввести его только в свой компонент верхнего уровня (между этими двумя). Если это не так, вы должны добавить больше кода и повторить, если это возможно. –

ответ

10

Что касается вашего последнего комментария, это самый простой способ, которым я могу думать: создать службу, которая будет иметь одно свойство и свойство будет содержать запрос.

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

Как простой. Все остальное в plnkr было бы нетронутым. Я удалил запрос из Сервиса, потому что он будет создан автоматически (мы не делаем new Service..., и я не знаю простого способа передать параметр через конструктор).

Итак, теперь у нас есть службы, что мы делаем сейчас сделать запрос на наш компонент и присвоить его переменной 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

Привет, хороший пример, но он не работает, например, если у вас есть запрос на событие click, он будет каждый раз делать новый запрос xhr. Ex: http://plnkr.co/edit/Z8amRJmxQ70z9ltBALbk?p=preview (нажмите на синий квадрат и соблюдайте вкладку сети). В моем приложении я создал новый ReplaySubject Observable для кэширования HTTP, я хочу использовать метод share(), но это странно, почему в некоторых случаях это не работает. – tibbus

+0

Ах, на самом деле я получаю его (после некоторых тестов и чтения rxjs docs), поэтому он будет использовать одно и то же наблюдаемое значение для всех существующих подписчиков, но после того, как нет подписки, и вы создадите новый, тогда он попросит новое значение, поэтому новый запрос xhr. – tibbus

+0

@ Эрик Мартинес: плункер больше не работает ... –

69

Оператор share() работает только по первому требованию, когда все подписки подаются и создать еще один, то он не будет работать, то это будет сделать другой запрос. (Этот случай является довольно распространенным, как и для angular2 SPA вы всегда создания/уничтожения компонентов)

Я использовал ReplaySubject для хранения значения от HTTP наблюдаемого. Наблюдаемый ReplaySubject может использовать предыдущее значение для своих абонентов.

служба:

@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
(наблюдения консоли и вкладку Network)

+0

Ответ Гюнтера очень полезен, но ваш кажется еще лучше, учитывая, что он использует чистую функциональность 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: можете ли вы поделиться своим кодом плункера? :) –

+0

@pdfarhad хорошая идея :) Обновлен ответ. Код содержал несколько ошибок, которые теперь должны быть исправлены. –

Смежные вопросы