Caching an Angular App - angularjs

What's the best way to cache things in Angular when you use ui-router? The problem is when switching between views it does all the $http requests again which is a bit pointless. Options I came up with or read about:
1) Just store everything to $rootScope.
Pro: Quickly implemented;
Con: Gets messy with too many objects (globals...)
2) Make a cache service
angular.module('myApp')
.factory('Cache', function($cacheFactory) {
var cache = $cacheFactory('Cache');
return cache;
});
To me this is the same as $rootScope since with Cache.put("someObject", obj); is pretty much the same as $rootScope.someObject = obj?
3) Cache via $http
$http.get(url, { cache: true}).success(...);
Pro: Doesn't use globals.
Con: Needs to be cleared when retrieved object is changed by user. Probably only good for kind of static content.
4) Make a service for every cached object
Are there other options? I find none of those above really satisfying...

Related

Store JSON file contents on load

Right now, I have a factory which loads a JSON file.
angular.module("app").factory("RolesFactory", ['$http', '$q', function($http, $q) {
var d = $q.defer();
$http.get('events.json').success(function(data) {
d.resolve(data);
});
return d.promise;
}]);
And then I call this factory when I need the contents of events.json with this controller:
App.controller('rolesCtrl', ['$scope', 'RolesFactory', function($scope, RolesFactory) {
RolesFactory.then(function(roleData){
$scope.roles = roleData.roles;
});
}]);
All good, but whenever I need to use this data. Isn't it refetching the contents of events.json? Meaning: is Angular reloading the file over and over again? I was hoping to load the file once and call it by a global variable or something.
When my app loads initially, I want it to load and store the contens of events.json -- and then I'd like my app to be able to use this data whenever/wherever.
Is this possible?
As AngularJS is a stateless framework, you have only a few options here, all of which are some kind of client-side caching:
Use localStorage to store your data. Once the data is fetched, you can just save it to localStorage using localStorage.setItem after Stringifying the JSON. You'll need to re-parse the JSON the next time you use it though, so if this is a giant JSON, this is not the best idea
Use sessionStorage to store your data. This is exactly the same as #1, but you will lose data upon termination of session,i.e. closing your browser.
Trust the JSON to be cached in your browser. This is most likely the case. Static assets are by default cached by most modern browsers. So, the second time your factory requests the JSON, the resource isn't actually fetched from the server. It is merely pulled from the browser's cache.
NOTE: The way to check this is to see what the HTTP status code for your resource is, in Chrome's Developer Tools Network tab. If the status says 304 that means it has been pulled from cache.

$cacheFactory deletes my cache when page reloads

So I have a simple factory that creates cache object
.factory('jsonCache',function($cacheFactory){
console.log("Creating cache");
return $cacheFactory('weatherCache');
});
I'm then calling that cache object inside my controller like so by passing it in like so. But for some reason the data is not persistent. Every time I reload the page, the cache is empty again.
Any ideas? Did I miss something?
.controller('citiesListCtrl',function($scope,$http,$filter,jsonCache){
jsonCache.get('weatherCache');
console.log(jsonCache); <----- EMPTY
$http.get(url)
.then(function(response) {
jsonCache.put('weatherCache', response.data.records);
console.log(jsonCache); <--- HAS DATA
});
)}
The data is not persistent for very good reason: $cacheFactory cache is nothing but a thin wrapper around plain JS object.
You can check the source to make sure that the service does nothing but simple LRU algorithm.
For data persistence use persistent storage (probably angular-local-storage or angular-localForage). angular-cache is an another replacement for built-in $cacheFactory that supports persistence.

angularjs, ngResource in a factory and network calls

Trying to get my head around this. I have a simple factory using ngResouce, like this:
.factory('FooResource', function($resource) {
var foo = $resource('/api/foo').get();
return foo;
})
And in my app, in multiple places, in multiple controllers over time, I use the value of 'FooResource.bar' (where 'bar' is returned in the data from the get() call).
Is it true that the network call to '/api/foo' will only happen on the first reference for the life of my SPA? Does that first reference need to 'FooResource.bar' be handled like a promise?
From what I see in my playing around with code, it seems like the first question is 'yes' and the second is 'no', but don't know if that's really true in general, or just happening because its a small test app on my dev box.
Edit: I guess part of what I want validation on is my thinking that since this is in a factory, which is a singelton, the $resource call will only ever be made once. Is that true?
Depends, and yes. You will always need to handle it as a promise, and you can enable/disable the http cache. If you have the cache set to true, then the request will send off once and be cached until the cache is cleared.
You can find more about the $resource caching in the $resource documentation here: https://docs.angularjs.org/api/ngResource/service/$resource

AngularJS Download Settings from Servers

We have an endpoint on our API that includes a set of settings (like default text, other endpoints, etc.). Our frontend is written in AngularJS and we're trying to figure out the best way to get them back to the client, and make them available throughout all directives in the application. Right now our best solution is to include settings as a directive:
angular.module('ourapp')
.factory('settings', function ($http) {
var url = 'http://localhost:8080/settings';
return function (callback){
$http.get(url).success(callback);
};
});
But then all the other calls are wrapped asynchronously.
Is there a better way to do this?
Since the settings come asynchronously from the server, their availability will inherently be asynchronous. If your logic depends on the settings being available, then there is probably no better solution than using promises.
angular.module('ourapp').factory('settings', function($http) {
var url = 'http://localhost:8080/settings';
return $http.get(url); // returns a promise
});
You could use $route to resolve the promise before instantiating controllers. The settings would then be synchronously available in the controllers.
You can also simulate promise unwrapping, i.e. immediately (synchronously) returning an object, which later will be filled with real data. This is great for scopes and templates, and was previously a feature of Angular itself. Be aware that the simulated promise unwrapping may cause bugs if not used cautiously, because the settings data may or may not be there.
Example:
angular.module('ourapp').factory('settings', function($http) {
var url = 'http://localhost:8080/settings';
var settings = {};
$http.get(url).success(function(data) {
angular.extend(settings, data); // fills in data from server
});
return settings; // immediately (synchronously) returned
});

UI-Router : Can I get always the same controller?

I'm using UI-Router to have some "menus" in my application.
$stateProvider.state("list"
url: "/Focales"
templateUrl: "/demo/focals.html"
controller: FocalCtrl)
$stateProvider.state("collection"
url: "/Collections"
templateUrl: "/demo/collections.html"
controller: CollectionCtrl)
In my CollectionCtrl, I trigger a processing done on server and just waiting to display the information like this (CoffeeScript)
Progress = $resource('/collections/getProgress')
$scope.getProgress = () ->
prg = Progress.get {}, ->
$scope.currentProgress = prg
$scope.getProgress() if prg.end isnt true
My issue : When the user moves to Focal and goes back to CollectionCtrl, I have a new instance of CollectionCtrl. So as far as I understand, the $scope.getProgress code still receives data but for the PREVIOUS CollectionCtrl (so the display is not updated...)
Is it possible to get the previous Controller rather than a new "instance" of the CollectionCtrl ? Why is a new CollectionCtrl created ?
What's the best approach: So I'm tempted to have a state data to store the current $scope so I could do $state.currentScope = prg rather than $scope.currentProgress = prg.
Is it a good approach ?
Thanks !
The best approach to take here is to define a service which will communicate with the server and possibly cache the results. This service can be injected in the Controllers and the controllers can ask it for the progress, probably using $watch, though providing the service with a callback may also suffice, and update the UI based on that.
Controllers should be ephemeral and one should not rely on them being singleton-ish objects in Angular. Importantly, they should not contain any state apart from that needed by the template they are supporting. The are created again when the route becomes active so that we can write comparatively simpler code, initializing the $scope once and rest assured that it will contains as fresh information (w.r.t the route) for the template as possible. This also avoids memory leaks if a controller is kept around for each route like /app/:object_id and the user switches routes often.

Resources