211

I want to be able to create a custom AngularJS service that makes an HTTP 'Get' request when its data object is empty and populates the data object on success.

The next time a call is made to this service, I would like to bypass the overhead of making the HTTP request again and instead return the cached data object.

Is this possible?

DavidRR
  • 18,291
  • 25
  • 109
  • 191
Gavriguy
  • 3,068
  • 3
  • 19
  • 23

7 Answers7

318

Angular's $http has a cache built in. According to the docs:

cache – {boolean|Object} – A boolean value or object created with $cacheFactory to enable or disable caching of the HTTP response. See $http Caching for more information.

Boolean value

So you can set cache to true in its options:

$http.get(url, { cache: true}).success(...);

or, if you prefer the config type of call:

$http({ cache: true, url: url, method: 'GET'}).success(...);

Cache Object

You can also use a cache factory:

var cache = $cacheFactory('myCache');

$http.get(url, { cache: cache })

You can implement it yourself using $cacheFactory (especially handly when using $resource):

var cache = $cacheFactory('myCache');

var data = cache.get(someKey);

if (!data) {
   $http.get(url).success(function(result) {
      data = result;
      cache.put(someKey, data);
   });
}
Nate Anderson
  • 18,334
  • 18
  • 100
  • 135
asgoth
  • 35,552
  • 12
  • 89
  • 98
  • 1
    Is it possible to implement the cache with localStorage? – Marc Jan 02 '13 at 12:31
  • 1
    If I check angular's code: no. But it should be quite easy to create a service (localStorageService) which stores in localStorage - if available - and in $cacheFactory - if not. – asgoth Jan 02 '13 at 12:40
  • Yep, I hacked it into a $cacheFactory instance https://groups.google.com/forum#!topic/angular/D2LcJ9hoHKU – Marc Jan 02 '13 at 18:09
  • @Marc: Nice! You could create a [pull request](http://docs.angularjs.org/misc/contribute). – asgoth Jan 02 '13 at 18:21
  • 47
    Question: what's the point of saving cached data into $cacheFactory.. why not just save it into a local object in the Service? Any good reasons? – Spock Sep 10 '13 at 10:36
  • The first and second methods do not seem to be caching anything for me. It is still generating another ajax call. IS that expected behavior? – Ray Suelzer Oct 20 '13 at 22:55
  • 7
    Check this out. It gives you lots of customizability including localStorage support, timeout support, all kinds of goodies [http://jmdobry.github.io/angular-cache/](http://jmdobry.github.io/angular-cache/) – Erik Donohoo Feb 01 '14 at 18:06
  • thats awesome man.. it not only caches the result but also saves to scroll... you made my day!!! – Ibrahim Benzer Sep 14 '14 at 14:44
  • 4
    I'm especially curious about status code 304 - does browser cache work without enabling cache:true? If not, does cache:true make it work? Is caching permanent or it's just in RAM and is unloaded when the page is closed? – sasha.sochka Feb 04 '15 at 00:01
  • 3
    Any way to specify a time limit on this cache without manually implementing it? – Mark Mar 13 '15 at 18:25
  • 2
    Any way to check the data from fresh copy or cache when using `{ cache: true}`? – geckob Jun 26 '15 at 21:16
  • 11
    @Spock, $cacheFactory itself is a service that can be used across multiple controllers and angular components. It can be used as a generic api service to cache all your $http's into a single service obj rather than having different service objects for each one of them. – Nirav Gandhi Jul 09 '15 at 05:10
  • Can we catche before make http request? – Jeeva J Oct 06 '15 at 05:25
  • @asgoth what does the `cache : true` do? If the results from the http request are cached, how can I retrieve them without having to make another http request? How does it work? – user3871 Aug 29 '16 at 20:47
  • @Growler `cache: true` tells the $http service to cache the results of the request. Subsequent calls check the cache if it exists and return the results from there instead of making a new request from the server. By default the key for the request is the URL used when making the $http call. – Jeff Adams Jun 21 '17 at 16:40
  • Warning: this does not respect the `Cache-Control` header (a la https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.1.3). > "A cache-control header on the response does not affect if or how responses are cached." – Nick Grealy Mar 28 '18 at 08:38
  • @Spock another benefit of $cacheFactory is that there is less boilerplate for get requests because you only have to specify it on the config argument. No need to manually save the cache in a then callback. – Gabriel Smoljar May 09 '18 at 15:49
  • Another reason of adding cache to a service over working with localstorage is because localstorage has it's own capacity, so if you don't want to overload this space you better use $cache, it´s benefitial to save space. – Alvaro Castro Apr 06 '21 at 22:01
48

I think there's an even easier way now. This enables basic caching for all $http requests (which $resource inherits):

 var app = angular.module('myApp',[])
      .config(['$httpProvider', function ($httpProvider) {
            // enable http caching
           $httpProvider.defaults.cache = true;
      }])
gspatel
  • 1,128
  • 8
  • 12
12

An easier way to do this in the current stable version (1.0.6) requires a lot less code.

After setting up your module add a factory:

var app = angular.module('myApp', []);
// Configure routes and controllers and views associated with them.
app.config(function ($routeProvider) {
    // route setups
});
app.factory('MyCache', function ($cacheFactory) {
    return $cacheFactory('myCache');
});

Now you can pass this into your controller:

app.controller('MyController', function ($scope, $http, MyCache) {
    $http.get('fileInThisCase.json', { cache: MyCache }).success(function (data) {
        // stuff with results
    });
});

One downside is that the key names are also setup automatically, which could make clearing them tricky. Hopefully they'll add in some way to get key names.

exclsr
  • 3,329
  • 23
  • 27
James Skemp
  • 8,018
  • 9
  • 64
  • 107
7

Check out the library angular-cache if you like $http's built-in caching but want more control. You can use it to seamlessly augment $http cache with time-to-live, periodic purges, and the option of persisting the cache to localStorage so that it's available across sessions.

FWIW, it also provides tools and patterns for making your cache into a more dynamic sort of data-store that you can interact with as POJO's, rather than just the default JSON strings. Can't comment on the utility of that option as yet.

(Then, on top of that, related library angular-data is sort of a replacement for $resource and/or Restangular, and is dependent upon angular-cache.)

Oliver Salzburg
  • 21,652
  • 20
  • 93
  • 138
XML
  • 19,206
  • 9
  • 64
  • 65
  • 3
    Please note that `angular-data` is deprecated now. The latest on is `js-data-angular` http://www.js-data.io/v1.8.0/docs/js-data-angular – demisx May 28 '15 at 06:37
  • The angular-cache library has the features that should have been built into Angular's $cacheFactory. The built-in solution seems almost useless given it's limitations in being able to expire specific caches. The angular-cache factory was one of the easiest 3rd party libraries to implement as well. – Darryl Oct 19 '15 at 17:01
5

As AngularJS factories are singletons, you can simply store the result of the http request and retrieve it next time your service is injected into something.

angular.module('myApp', ['ngResource']).factory('myService',
  function($resource) {
    var cache = false;
    return {
      query: function() {
        if(!cache) {
          cache = $resource('http://example.com/api').query();
        }
        return cache;
      }
    };
  }
);
Rimian
  • 36,864
  • 16
  • 117
  • 117
  • I have one question how to check if GET failed and in that case not to put into cache the $resource...query() – robert Jul 07 '16 at 21:31
  • @robert you can check on the second argument of the .then method or better yet, use the .catch callback. For example $http .get(url) .then(successCallback, failCallback) or $http .get(url) .then(successCallback, failCallback) .catch(errorCallback) The error callback will be executed even if something bad happens in the failCallback, although it's more common to avoid the fail callback at all and use .then(success).catch(manageRequestFail). Hope that helps to grasp the idea, more info in the angular $http documentation. – Faito Sep 19 '16 at 14:15
2
angularBlogServices.factory('BlogPost', ['$resource',
    function($resource) {
        return $resource("./Post/:id", {}, {
            get:    {method: 'GET',    cache: true,  isArray: false},
            save:   {method: 'POST',   cache: false, isArray: false},
            update: {method: 'PUT',    cache: false, isArray: false},
            delete: {method: 'DELETE', cache: false, isArray: false}
        });
    }]);

set cache to be true.

Howardyan
  • 667
  • 1
  • 6
  • 15
  • This would be as secure as the client application withing the browser itself just like any other web app. – bhantol Nov 09 '16 at 16:10
-2

In Angular 8 we can do like this:

import { Injectable } from '@angular/core';
import { YourModel} from '../models/<yourModel>.model';
import { UserService } from './user.service';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class GlobalDataService {

  private me: <YourModel>;

  private meObservable: Observable<User>;

  constructor(private yourModalService: <yourModalService>, private http: HttpClient) {

  }

  ngOnInit() {

  }


  getYourModel(): Observable<YourModel> {

    if (this.me) {
      return of(this.me);
    } else if (this.meObservable) {
      return this.meObservable;
    }
    else {
      this.meObservable = this.yourModalService.getCall<yourModel>() // Your http call
      .pipe(
        map(data => {
          this.me = data;
          return data;
        })
      );
      return this.meObservable;
    }
  }
}

You can call it like this:

this.globalDataService.getYourModel().subscribe(yourModel => {


});

The above code will cache the result of remote API at first call so that it can be used on further requests to that method.

Raghav
  • 8,772
  • 6
  • 82
  • 106