Cache HTTP responses in IONIC2


#1

I have a page which makes http requests to the same location, just with different parameters depending on what the user wants. So my code looks something like this:

this.http.post( //I am open to POST methods or GET methods as I have security in the back to prevent malicious writes.
   'http://192.168.1.45:3000/mylocation',
   'p1=' + param1 + '&p2=' + param2, 
   {headers: headers}
)

In JQuery for example you have build into the framework a cache attribute which caches automatically and is very easy to implement:

$.ajax({
  cache: true,
  //other options...
});

Does Angular2 have something similar to this? I would like to cache these dynamic responses as long as the user is in the application. So if a user requests the same url with the same parameters then it would just grab it from the cache, and if the params were never yet used then it would make the network call.


#2

In jQuery the cache option is explicitly to ignore browser caching. You do not need to set cache: true because your browser will automatically cache the response as long as your web server has caching turned on.

I guess it seems unlikely that Angular 2 would do anything to disable browser caching if the response header said caching was on. I could be wrong about this though, it’s not clearly documented in Angular 2 yet.

So:

  1. You should open up dev tools in chrome and watch on the network tab and see if your response is cached already, it may be.
  2. You could also handle this yourself by storing the response in a variable in the service. This of course get’s too complicated though if you’re calling the endpoint with lots of different parameters.

#3

You should also make sure the server is setting the headers that allow caching. Generally speaking, POST responses aren’t considered cacheable.


#4

I have a simple cache service which may suit your needs @polska03

I use it to cache frequent GET requests to a local key/value pair. If the cache is expired or doesn’t yet exist for that key, then it grabs and stores the result (with the option of passing in a TTL for the cache in seconds, or none for default TTL.)

I also have an event I subscribe to in order to invalidate the whole cache when I make any dynamic updates that I can publish to when I need to clear out the cache completely.

Hope this helps, you should be able to tweak to your needs if you find it useful. It currently only does GET requests but so far that’s all I’ve needed it for.

// app/services/cache/simple_cache.ts

import {Storage, SqlStorage, Events} from 'ionic-angular';
import {Http} from 'angular2/http';
import {Injectable} from 'angular2/core';
import {API_URL} from '../../config'; // this is my API URL, change it to yours


const CACHE_TTL = 60 * 60; // 1 hour

@Injectable()
export class SimpleCacheService {

  private storage: Storage;

  constructor(private events: Events, private http: Http) {

    events.subscribe('cache:invalidate', () => {
      this.cacheInvalidate();
    });

    this.storage = new Storage(SqlStorage);

  }


  /**
   * Invalidate Cache
   * @returns {Promise<any>}
   */
  public cacheInvalidate(): Promise<any> {
    return this.storage.clear();
  }

  /**
   * Get Cached Item
   * @param {string} name - Cache key
   * @param {string} location - API endpoint of cached item
   * @param {number=} ttl - TTL in seconds (-1 to invalidate immediately)
   * @returns {Promise<{}>}
   */
  public getItem(name: string, location: string, ttl?: number): Promise<{}> {

    // if ttl is < 0, delete cached item and retrieve a fresh one
    if (ttl < 0) {
      this.storage.remove(name);
    }

    return new Promise((resolve, reject) => {

      this.storage.get(name).then(cachedResult => {

        if (typeof cachedResult !== 'undefined') {

          // something's in the cache
          let data = JSON.parse(cachedResult);

          if (this.itemExpired(data)) {
            // cache IS expired
            this.load(location)
              .then(res => this.setItem(name, res, ttl).then(() => resolve(data.data)))
              .catch(err => reject(err));
          } else {
            // cache is NOT expired
            resolve(data.data);
          }

        } else {

          // not in the cache (key doesn't exist)
          this.load(name)
            .then(res => this.setItem(name, res, ttl).then(() => resolve(res)))
            .catch(err => reject(err));
        }

      }).catch(err => reject(err));

    });
  }


  /**
   * Set Cached Item
   * @param {string} name - Key name of item to store
   * @param {any} data - Value of data to store
   * @param {ttl=} ttl - TTL in seconds
   */
  public setItem(name: string, data: any, ttl?: number): Promise<{}> {
    let expiration = (typeof ttl !== 'undefined' && ttl) ? this.currentTimestamp() + ttl : this.currentTimestamp() + CACHE_TTL;
    let value = JSON.stringify({ data: data, expires: expiration});
    return this.storage.set(name, value);
  }

  /**
   * Delete Cached Item
   * @param {string} name - Key name of item to delete
   * @returns {Promise<any>}
   */
  public deleteItem(name: string): Promise<any> {
    return this.storage.remove(name);
  }


  /**
   * Check Item for Expired Cache
   * @param {data: any, expires: number} item - Cache item
   * @returns {boolean}
   */
  private itemExpired(item: {data: any, expires: number}): boolean {
    return (typeof item !== 'undefined' && typeof item.expires !== 'undefined') ?
      this.currentTimestamp() > item.expires : true;
  }


  /**
   * Get Current Timestamp
   * @returns {number}
   */
  private currentTimestamp(): number {
    return Math.floor(new Date().getTime() / 1000);
  }


  /**
   * Load from API Endpoint
   * @param {string} path - Endpoint to grab
   */
  private load(path: string): Promise<{}> {
    return this.http.get(API_URL + path).map(res => res.json().data).toPromise();
  }

}

which you would call using something like this:

// app/components/some_component.ts
import {IONIC_DIRECTIVES} from 'ionic-angular';
import {Component} from 'angular2/core';
import {SimpleCacheService} from '../../services/cache/simple_cache';


@Component({
  ...
})
export class SomeCmpt {

  private filters: any[];
  private morefilters: any[];

  constructor(private simpleCache: SimpleCacheService) {

    // where 'filters' would be the key in cache
    // and 'filters/list' would be the endpoint of your API

    simpleCache.getItem('filters', 'filters/list')
      .then(res => this.filters = res)
      .catch(err => console.log('oops', err));

    // you can also pass in an expiration time in seconds
    simpleCache.getItem('morefilters', 'filters/list/more', 60 * 60 * 24)
      .then(res => this.morefilters = res)
      .catch(err => console.log('oops', err));

  }

}

#5

Thanks! This is very helpful!