Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Caching Data From HttpClient in Angular 4

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']);
        });
  }
like image 498
Joseph Avatar asked Sep 26 '17 01:09

Joseph


2 Answers

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.
like image 121
Martin Schneider Avatar answered Sep 23 '22 20:09

Martin Schneider


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

like image 33
Leo Avatar answered Sep 19 '22 20:09

Leo