$timeout guarantees when implementing a cache service in angular js - javascript

I am implementing my own cache because I need it to be aware of the semantics of my application. I am implementing the cache within a client API that access a REST service. The logic is simple: I first look into my own dictionary of objects, and if the requested object is not present, then I use the network to request the object from the REST service. The code using the client API will expect a promise, even if the requested object is in my cache. My question is: What is the best way of implementing a promise/deferred pattern for the objects in the cache? Right now I am using a timeout function to do that. Is the timeout function good enough:? What guarantees do I have that the timeout function will execute after the user of the API receives the promise object? To look into the details of my question, please, see the code below:
Simplifying code to show only what is relevant to the question:
angular.module("myApp").factory("restClient", function($q, $http, $timeout, cache){
var get_content=function(link){
var deferred=$q.defer();
$http.get(link)
.success(function (data) {
deferred.resolve(data);
deferred=null;
})
.error(function (error) {
deferred.reject(error);
deferred=null;
});
return deferred.promise;
};
return{
getObject : function(objectId) {
//If object is in the cache
if (cache.contains(objectId)){
var deferred=$q.defer();
$timeout(function (){
var objectContent=cache.get(objectId);
deferred.resolve(objectContent);
deferred=null;
});
return deferred.promise;
}
else{
//Create link
//return promise
return get_content(link);
}
}
});

With what you are trying to do, one important thing is not to create a redundant deferred promise object when you already have objects that returns a promise, ex:- $http and $timeout. You could do:-
var get_content=function(link){
return $http.get(link)
.then(function (response) {
cache.put(link, response.data);
return response.data;
}, function(response){
return $q.reject(response.data);
});
};
return {
getObject : function(objectId) {
//If object is in the cache
if (cache.contains(objectId)){
return $q.when(cache.get(objectId))
}
return get_content(objectId);
}
Or even better instead of placing the data in the cache you could put the promise itself onto the cache.
getObject : function(objectId) {
if (cache.contains(objectId)){
return cache.get(objectId);
}
var promise = $http.get(objectId)
.then(function (response) {
//Incase you have any condition to look at the response and decide if this is a failure then invalidate it here form the cache.
return response.data;
}, function(response){
cache.remove(objectId); //Remove it
return $q.reject("Rejection reason");
});
}
cache.put(objectId, promise);
return promise;

Related

Getting empty return result in $http.get() in AngularJS 1.5.0

I have created a factory in my AngularJS application which is used to bring in refreshed data from server after a delete has been performed on one or more elements of the list.
var myApp = angular.module('app');
myApp.factory('mvListRefresher', function($http) {
return {
refreshCourses : function() {
var list = new Array();
$http.get('/api/courses').then(function(response) {
console.log('got refreshed lists');
console.log(response.data);
list = response.data;
}, function(error) {
console.log('error in retreiving fresh lists');
console.log(error);
});
console.log('Displaying list of courses');
console.log(list);
if(list.length != 0) {
return list;
}
}
}
});
I'm calling this factory in one of my controllers by calling
$scope.courses = mvListRefresher.refreshCourses();
But I'm getting an empty list, i.e I'm not getting any return value at all. In my factory function I observed that the line
console.log(list);
always prints out an empty array, however the line
console.log(response.data);
which is inside the success callback prints out the full list of objects correctly. I don't know how this is happening. I'm relatively new to AngularJS's promises and it's asynch methods and architecture. Kindly help me out. If you need any more info i'll provide it. I've done the entire app using MEAN. Thanks in advance!
$http requests are asynchronous by nature so at the point you're returning list, the request is not complete yet. You can read more about the general concepts of asynchronicity in JavaScript in this response: https://stackoverflow.com/a/14220323/704894
What you should do instead is return a Promise of list:
myApp.factory('mvListRefresher', function($http) {
return {
refreshCourses: function() {
return $http.get('/api/courses').then(function(response) {
return response.data;
});
}
};
});
$http methods return promises of results. Then you can use this function in the following way:
mvListRefresher.refreshCourses().then(function (list) {
$scope.courses = list;
});
You can read more about it in the $http documentation.

How to write global function for server error and success in angular js

I have a separate functions like, submit,update,delete. for all function
$http[method](url)
.then(function(response) {
$scope.successMessage = true;
} , function(response) {
$scope.errorMessageWrong=true;
});
html
<p ng-show="successMessage">Success</p>
<p ng-show="errorMessageWrong"> Something went wrong </p>
For separate functionalities. i need to show the corresponding messages. but i don't want to repeat the code for update, delete and submit and even the same thing for the other pages which do the same operation.
how to create function called errorHandler or something. so that i can reuse it.
can anyone help me
how to create function called errorHandler or something. so that i can reuse it.
Create chainable promises by returning for fulfilled data responses and throwing rejected error responses.
The example function below takes an httpPromise as an argument, puts success or error messages on $scope, and returns a promise suitable for chaining.
function errorHandler(httpPromise) {
var derivedPromise = httpPromise
.then(function onFulfilled(response) {
$scope.successMessage = true;
//return response for chaining
return response;
},
function onRejected(errorResponse) {
$scope.errorMessageWrong = true;
//throw error to chain rejection
throw errorResponse;
});
//return derivedPromise for chaining
return derivedResponse;
};
Then in client code:
var httpPromise = $http[method](url);
errorHandler(httpPromise).then( function (response) {
//use data
});
The client code saves the httpPromise from the $http service call, processes the promise with the errorHandler function, and the uses the derived promise returned by the errorHandler function.
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
If you want it global to your app, then you can use an httpInterceptor.
You have to create an interceptor service and then add the interceptor to the $httpProvider in your app.config().
Create your interceptor service:
angular.module('app').factory('myInterceptorService', myInterceptorService);
function myInterceptorService($q){
var errorMessage;
var bShowHideWatchFlag;
return{
requestError: requestError,
responseError: responseError,
showFlag: bShowFlag,
errorMessage: errorMessage
};
function requestError(rejection){
errorMesasge = 'Request error';
bShowHideWatchFlag = true;
$q.reject(rejection);
return;
}
function responseError(rejection){
errorMesasge = 'Response error';
bShowHideWatchFlag = true;
$q.reject(rejection);
return;
}
}
To register with app config, add $httpProvider to app.config
app.config([...,'$httpProvider'...){
$httpProvider.interceptor.push('myInterceptorService');
}
In your controller, you have to bind a watch to the service showFlag:
$scope.$watch( function () { return myInterceptorService.showFlag; },
function (oldval,newval) {
if( oldval!=newval){
$scope.errorMessage = myInterceptorService.errroMessage;
$scope.showMessage = newval;
}
}, true);
You can use service for this and share within various controller. $http is also a service. But if you want to add something more to the data you can create a new service and inject $http to it.

Angular caching in localStorage

I am trying to write a service in Angular that will cache data to localStorage, without much success! I need your help. I have several "lookup" datatypes that don't change very frequently, such as broker codes and airline codes.
I remember reading in one of my (many!) tech books that it is possible to wrap a promise around any data type, and I am taking this approach in writing my service. Essentially, I want to return the value from localStorage if it exists, then return immediately with a resolved promise. Or, in the case that it doesn't exist, I want to $http.get() it and return the promise returned from this call.
My pseudocode would look like this:
angular.module('myApp')
.service("lookupDataService", function() {
this.brokers = function() {
var cachedBrokers = localStorage.getItem("brokers");
if (!cachedBrokers) {
$http.get('/api/brokers')
.success(function(data) {
localStorage.setItem("brokers", data);
// return promise
});
} else {
var deferred = $q.defer();
deferred.resolve();
// shoehorn the data from localStorage
return deferred;
}
}
})
Is this approach sound? How do I return a promise and the data to the calling behaviour?
EDIT
I realised I wasn't doing my dependency injection correctly. I have modified my pseudocode to look like this:
angular.module('myApp')
.service("lookupDataService", ['$q','$http', function($q, $http) {
this.brokers = function() {
var cachedBrokers = localStorage.getItem("brokers");
if (!cachedBrokers) {
return $http.get('/api/brokers')
.success(function(data) {
localStorage.setItem("brokers", data);
}
);
} else {
return $q.when(cachedBrokers);
}
}}]
)
It occurred to me that my method wasn't returning a promise until the internal promise was resolved. I think. Head-scratching moment.
M
I would look into NGStorage,http://ngmodules.org/modules/ngStorage.
<script>
angular.module('app', [
'ngStorage'
]).
controller('Ctrl', function(
$scope,
$localStorage
){
$scope.$storage = $localStorage.$default({
x: 42
});
});
</script>

loading JSON data from angular service

Please forgive me if this is a simply problem for an angular guru, i am fairly new to services.
Below is a snippet of my controller where i have attempted make a service request to call out data from my JSON file "jobs.json".
I am not receiving an data when i load my web page neither i am seeing the JSON file in inspector element.
I assume there's something incorrect in my below code. Does anyone what the issue is?
Click here if you need to play about with the code
"use strict";
var app = angular.module("tickrApp", []);
app.service("tickrService", function ($http, $q){
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data){
deferred.resolve(data);
});
this.getItems = function () {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getItems();
promise.then(function (data){
$scope.items= getData;
console.log($scope.items);
});
In your Plunkr, you had a few errors, such as the <script> tags around the wrong way (you need to have Angular first, so your code can then use angular.module). You also had the wrong attribute of ng-app-data instead of data-ng-app.
The key problem was with the JS code, the first parameter to the success handler for the $http.get() call is an object with a data property, which is the actual data returned. So you should resolve your promise with that property instead.
Then in the controller, like Michael P. said, getData is undefined, you should use the data parameter passed in.
app.service("tickrService", function($http, $q) {
var deferred = $q.defer();
$http.get('jobs.json').then(function(response) {
deferred.resolve(response.data);
});
this.getjobs = function() {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getjobs();
promise.then(function(data) {
$scope.jobs = data;
console.log($scope.jobs);
});
});
See forked Plunkr.
In the success handler of your getItems function, you are storing getData, which is undefined. You want to store data instead.
Therefore, in the controller, your call to getItems() should be as follows
tickrService.getItems().then(function (data) {
$scope.items = data;
});
Also, you want to make the $http call in getItems. Like that :
this.getItems = function () {
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data) {
deferred.resolve(data);
});
return deferred.promise;
}
However, you can avoid the above boilerplate code around the promises, because $http.get returns itself a promise. Your service and controller could be much more concise and less polluted by boilerplate code.
The service could be as simple as :
app.service("tickrService", function ($http) {
this.getItems = function () {
return $http.get('app/data/items.json');
}
});
And the controller could be shortened to:
app.controller('tickCtrl', function ($scope, tickrService) {
tickrService.getItems().then(function (response) {
$scope.items = response.data;
})
});
Please note that the response resolved by $http is an object that contains (link to doc) :
data – The response body transformed with the transform functions.
status – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – The configuration object that was used to generate the request.
statusText – HTTP status text of the response.
Therefore in the success handler of getItems we are storing response.data, which is the response body, and not the whole response object.

How to access async data in a factory across multiple controllers without using promises

I'm trying to store some user data in a service that will be accessible and modified across different controllers, and the data will be initially pulled via a $http call. Since the data has to be loaded, I've used promises in my previous code, but this can be quite irritating to write. For instance, even a basic getter function has to be written as the following
UserData.getData().then(function(data) {
//do something with the data
})
where UserData.getData() always returns a promise via deferred.promise (if the data has already been pulled, then resolve immediately). I'm wondering if there is anyway to split a) $http calls, and b) getter and setter methods into two different services so that if I call getter and setter methods from b), I don't need to wrap everything with then?
For instance, I'm trying to make UserFactory in charge of the $http call, and UserData in charge of getters and setters. However, I can't get the code to work since UserData.getData() will return undefined, and wondering if anyone can help? (I don't really want to have to use then everywhere).
angular.module('testApp', [])
//mocks a service that gets data from a server
.factory('UserFactory', function($timeout, $q) {
return {
getData: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({title: 'hello world'});
}, 1000);
return deferred.promise;
}
}
})
.factory('UserData', function(UserFactory) {
var data;
return {
//if already pulled, use existing data
getData: function() {
if (data) return data;
UserFactory.getData().then(function(res) {
data = res;
return data;
})
}
}
})
http://jsfiddle.net/QNLk2/1/
The then method is executed asynchronously so the return method as no effect there. That is why until the data has arrived every call to getData will return undefined.
You can try the following approach, where you can specify a callback if you want to be notified when data is ready, or simply wait for the data to be populated in the return variable.
.factory('UserData', function(UserFactory) {
var data = {};
return {
getData: function(callback) {
UserFactory.getData().then(function(res) {
angular.extend(data, res);
if (callback) {
callback(data);
}
});
return data;
}
}
})

Categories