Caching Data From HttpClient in Angular 4
Asked Answered
A

2

7

i have a problem in making my caching more simpler. I think there is a better way to do this. My problem is that i have to do this "caching" codes in every get() function which is results in longer codes. Anyone help on how to do this the best way? Thanks. Here's my codes below. What i did in my codes is i do the get() function in my news.service.ts to get data from the http and i subscribe it in my news-list.

news.service.ts

getAllNews() {

    if(this.newslist != null) {
      return Observable.of(this.newslist);
    } 

    else {

      return this.httpClient
        .get('http://sample.com/news')
        .map((response => response))
        .do(newslist => this.newslist = newslist)
        .catch(e => {
            if (e.status === 401) {
                return Observable.throw('Unauthorized');           
            }

        });
    }
  }

news-list.service.ts

 this.subscription = this.newsService.getAllNews()
      .subscribe(
        (data:any) => {
          console.log(data);
          this.newslists = data.data.data;
        },
        error => {
          this.authService.logout()
          this.router.navigate(['signin']);
        });
  }
Arand answered 26/9, 2017 at 1:45 Comment(5)
I don't see anything wrong with it...Profound
@Leo. Oh ok i'll just use it everytime i do the get() function? That would be the best solution?Arand
What exactly is your cache? newslists?Profound
@Leo. Yes. newlistsArand
I would suggest to implement generic caching functionality. I personally use caching in pretty much every project, so it would be nice to create one generic tested robust caching service and use it in every project. I suggest to use something like @MA-Maddin answer (but at least take care of params also), or using httpInterceptors like HERESpalding
L
3

If you meant to have something generic, that you can use for different API calls or services, then you could do something like this:

import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';

class CacheItem<T> {
  url: string;
  timestampCached: number;
  data: T;
}

@Injectable({
  providedIn: 'root'
})
export class MyCachedHttpClient {
  cache: CacheItem<any>[] = [];

  constructor(
    private http: HttpClient,
  ) { }

  get<T>(url: string, cacheTime?: number, forceRefresh: boolean = false)
  : Observable<T> {
    let cachedItem: CacheItem<T> = this.getCachedItem<T>(url);

    if (cachedItem != undefined && !forceRefresh) {
      let expireDate = cachedItem.timestampCached + cacheTime;
      if (Date.now() < expireDate) {
        return of(cachedItem.data);
      }
    }

    return this.http.get<T>(url).pipe(
      map(data => {
        if (cacheTime) { // if we actually want to cache the result
          if (cachedItem == undefined) {
            cachedItem = new CacheItem();
            cachedItem.url = url;
            this.cache.push(cachedItem);
          }
          cachedItem.data = data;
          cachedItem.timestampCached = Date.now();
        }
        return data;
      })
    );
  }

  private getCachedItem<T>(url: string): CacheItem<T> {
    return this.cache.find(item => item.url == url);
  }
}

And then just use MyCachedHttpClient instead of HttpClient everywhere.

Notes:

  • This is for Angular 6 / RxJS 6. See code from Edit History if you are below.
  • This is just a basic implementation that hides out many features of HttpClient's get() function since I didn't reimplemented the options parameter here.
Lollapalooza answered 31/3, 2018 at 23:49 Comment(2)
Afaik this code snippet wont work, there is no setting up url in CacheItem.Spalding
That's true. That was just a non-tested code of basic concept. I'm going to update it to a working one.Lollapalooza
P
1

I'm not too sure what the difference between news.service.ts and news-list.service.ts is but the main concept is that you need to separate concerns. The most basic separation you can do is by distiguishing "data providers" from "data consumers"

Data Providers

This could be anything that fetches and manages data. Whether in-memory cached data, a service call, etc. In your example, it seems to me that news.service.ts it's a Web API client/proxy for everything related to news.

If this is a small file you could move all news-related cache management to this file or...create another component that manages cache and wraps news.service.ts. That component would serve data from its cache, if the cache doesn't exist or has expired, then it calls on news.service.ts methods. This way news.service.ts's only responsbility is to make ajax requests to the API.

Here's an example without promises, observables or error handling just to give you an idea...

class NewsProvider{

    constructor(){
        this.newsCache = [];
    }

    getNewsItemById(id){
        const item = this.newsCache.filter((i) => {i.id === id});

        if(item.length === 1) return item[0];

        this.newsCache = newsService.getAllNews();

        item = this.newsCache.filter((i) => {i.id === id});

        if(item.length === 1) return item[0];
        else return null;
    }
}

Data Consumers

These would be any component that needs data, a news ticker in the home page, a badge notification somewhere in the navigation menu....there could be any components (or views) needing news-related data. For that reason, these components/views shouldn't need to know anything about where that data is coming from.

These components will use "data providers" as there main source of data

Summary

Cache only needs to be (and can be) managed in one single place as well as network-related operations

Profound answered 26/9, 2017 at 3:7 Comment(2)
Thanks, Leo. But i have tried this getAll() { if(!this.news) { this.news = this.httpClient.get<any>("sample/news") .map((response => response)) .publishReplay(1) .refCount(); } return this.news; }Arand
I used .map in the service.ts and subscribe it in the component.ts so i can display the data in component.html. I don't know if this is the best and shortest way or correct way of doing it.Arand

© 2022 - 2024 — McMap. All rights reserved.