Get data synchronously when cached or get it async when unavailable - angularjs

I have an AngularJS app that uses routing and views. When a particular view is loaded and controller instantiates I have to prepare some $scope model data. This data can be provided by a dependent service or when service doesn't have it, I make an async call to get it from the server.
When I finally do have this data I have to change it a bit and then put it on my $scope.
This I think perfectly falls into deferred/promise API. Getting data from the service is done using $resource service instance and is a promise already. The only problem I'm having is converting my synchronous code to a deferred/promise pattern.
Question
How can I change my synchronous code processing to become async so my function that provides data always returns a promise which would be immediately resolved when using sync code and after a while when asynchronously calling my server?
So process:
try getting data synchronously
if sync failed, get it async
success/fail
data available => manipulate it
data unavailable (either way) => reset state
What I tried
var getData = function() {
var defer = $q.defer();
defer.promise
.then(function () {
// return cached item
return dataCacheService.get("dataKey");
})
.then(function(data) {
// cache returned data?
if (!data)
{
// no? get it from server returning a promise
return dataResource.get({
set: "models",
id: $routeParams.id
});
}
})
.then(function (data) {
// server returned data?
if (!!data) // <= PROBLEM!!! This is not null, but is a Resource with unresolved promise?
{
// yes? fine. manipulate it
delete data.details;
delete data.type.description;
$scope.lists.set(data.type);
return data;
}
// no data. sorry...
$scope.resetType();
})
// something went wrong
.catch($scope.resetType);
// initiate deferred execution
defer.resolve();
return defer.promise;
}
...
$scope.model = {
item: getData()
};

You can make your service such that it always returns a promise, if the data is available it will return the promise immediately otherwise after a REST call. For example your service might look like:
var dataThatMayOrMayNotBeAvailable=null;
var getDataThatMayOrMayNotBeAvailable=function(){
var deferred = $q.defer();
if(dataThatMayOrMayNotBeAvailable){
deferred.resolve(dataThatMayOrMayNotBeAvailable);
}else{
$http({...}).success(function(data){
dataThatMayOrMayNotBeAvailable=data;
deferred.resolve(data);
});
}
return deferred.promise;
}
Usage:
getDataThatMayOrMayNotBeAvailable().then(function(data){
console.log(data);
})

Related

Angular promise callback not firing

I have this app that uploads a file to a server using $cordovaFileTransfer and then sends data about the file to the same server. The file is transferred fine. The data is then sent to the server, and the server responds. But the response does not make it back to the promise callback. Why?
$scope.sendPost = function(data) {
//first upload a file then send more data about the file
$cordovaFileTransfer.upload('http://example.com', 'myfile.txt', options)
.then(function(result) {
var promise = MyFactory.sendFileData(data);
});
promise.then(function(response) {
//we never make it to here
});
}
and in MyFactory:
service.sendFileData = function(data) {
return $http({
//bunch of parameters. This function works, data is sent to the server and a response received
}).then(function(response) {
//this is fired when the response is received from the server. All is good so far.
return.response.data
});
}
return service;
$cordovaFileTransfer.upload returns a promise object, which you could use to build up promise chaining mechanism.
Code
$scope.sendPost = function(data) {
//get hold on `upload` function promise
var promise = $cordovaFileTransfer.upload('http://example.com', 'myfile.txt', options)
.then(function(result)) {
//return MyFactory.sendFileData promise here which will follow promise chaining
return MyFactory.sendFileData(data);
});
//promise.then will get call once `MyFactory.sendFileData` complete it
promise.then(function(response) {
//will get called once `sendFileData` complete its promise
});
}
its because you're relaying on another promise's callback to initiate a the promise and.. most probably before the promise gets initialized you are attaching a callback tot it.. so at the time of you attaching the callback, the promise is not yet initialized i.e. promise is null.. so in your console you'll see an error..
try doing some thing like
var x = function(response) {
//we'll make it to here now...
}
$cordovaFileTransfer.upload('http://example.com', 'myfile.txt', options)
.then(function(result)) {
var promise = MyFactory.sendFileData(data);
promise.then(x);
});
You should follow #PankajParkar solution though it's a better approach...
$scope.sendPost = function(data) {
//first upload a file then send more data about the file
$cordovaFileTransfer.upload('http://example.com', 'myfile.txt', options)
.then(function(result)) {
return MyFactory.sendFileData(result.data);
})
.then(function(response) {
});

Is it okay to handle all the $http errors in controller?

In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,
MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));
My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,
Scenario 1:
function getData(url){
return $http.get(url);
}
Scenario 2: (Nested calls to make combined results)
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?
Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.
Yes what you have can fail triggering your catch because you have no reject().
You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.
To be able to chain these get rid of the $q.defer() and do:
function getOtherData(url) {
// return beginning of promise chain
return $http.get(url).then(function (response) {
// return next promise
return $http.get(nextService).then(function (res) {
// combine and return the data
return {
d1 : response.data,
d2 : res.data
};
});
});
}
Now walk through the scenarios and each part of chain is intact.
Think of the chain as each then needs a return until the end of the chain
Scenario 2: (Nested calls to make combined results)
Failed Scenario
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.
Instead return data and chain promises.
function getOtherData(url) {
var promise = $http.get(url);
var derivedPromise =
promise.then ( function (response) {
var data = response.data;
var nextPromise = $http.get(nextService);
var derivedNext = nextPromise.then(function(response) {
//return for chaining
return response.data;
});
//return composite for chaining
return $q.all([data, derivedNext]);
});
return derivedPromise;
};
The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.
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
Chaining error handlers
In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.
For example:
promise = http.get(someUrl);
derivedPromise = promise.catch(function(errorResponse) {
if (fixable) {
fixedPromise = $http.get(fixedUrl);
//return to convert
return fixedPromise;
} else {
//throw to chain rejection
throw errorResponse;
}
};
By chaining error handlers, errors can be handled both by the service and the client of the service.
This makes it possible to implement powerful APIs like $http's response interceptors.1
Building on #georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = $http.get(nextService).then(function(response) {
return response.data;
});
return $q.all([promise1, promise2]);
};
Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.
This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.
Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.
MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));
Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = promise1.then(function(response) {
// compute nextService from response.data here...
var nextService = foo(response.data);
return $http.get(nextService).then(function(response) {
return response.data;
});
});
return $q.all([promise1, promise2]);
};

Angular storing server data in a global service. How do I ensure it is there

I have a globalDataService in my app that reads a couple of entities from the server.
I only want to read the data once, and then serve it up via a method on the service. Here's a simplified version
angular.module("myApp").factory("globalData", ["siteResource", globalData]);
function globalData( siteResource) {
var sites = [];
siteResource.query().$promise.then(function(data){
sites = data;
},
function(response) {
//handle bad stuff
});
var getSites = function () { return sites; }
return { getSites: getSites };
}
and in my controller I just want to be able to do this
this.sites = globalData.getSites();
and know that the data is there, and if it isn't then something is wrong. What do I need to do in my service to make this happen, I've just wasted 2 hours trying to do something with $q but with no joy.
It's pot luck whether the globalData service has loaded the data or not when I need it, particularly when the app first loads....
Save the promise and return the promise. Create the promise once and reuse it.
angular.module("myApp").factory("globalData", ["siteResource", globalData]);
function globalData(siteResource) {
var promise;
function getSitesPromise () {
if (!promise) {
promise = siteResource.query().$promise;
promise = promise.catch( function (error) {
//handle bad stuff
});
};
return promise;
};
return { getSitesPromise: getSitesPromise };
}
What your are doing is pretty simple.
Prob making $http requests to your server to get data. But you don't want to make them every time just on init.
you can use $q like this ...
var promise1 = $http.get('/mypath1');
var promise2 = $http.get('/mypath2');
var promises = $q.all([promise1, promise2]); // resolves when all promises are resolved OR will reject when ONE promise rejects
promises.then(function(arrayContainingAllServerResponses) {
// do something
}).catch(function(error) {
// oops one of the requests failed
})
sorry but i dont have time for more detail - this might get you on the right track - cheers
this works because $http returns a promise :)
Remember if your have a promise then you can call THEN on it - the THEN code will be executed when the promise is resolved (not immediately). You can even chain THENs by returning a promise from a THEN. Hope this all helps.
If a method returns a promise then you can call THEN on its return value. REMEMBER $http returns a promise which is resolved or rejected when your server responds or the request times out!

Service which returns multiple promises based on data from single HTTP request

I wish to implement a service which has the following features:
Performs a HTTP request to a given resource only once.
Provides two exported functions, each of which return a promise, where the promise is resolved based on data from the one-time HTTP call.
The two functions shall exist to provide different representations of the fetched data.
The outline of my service so far is as follows:
angular.module('someApp')
.service('someService', function ($http) {
var httpPromise = $http.get('/some/endpoint/').then(
function(response) {
// HTTP response data is processed
});
this.getSomePromise = function() {
// Return a promise which is resolved using one representation of HTTP response
}
this.getAnotherPromise = function() {
// Return a promise which is resolved using another representation of HTTP response
}
});
If there was only the need for one 'getter' function then clearly I could simply have returned the httpPromise.
What is the appropriate way to implement the interface as shown? Is it possible for two or more consumers to call .then() on the same promise, in which case I could merely return httpPromise.then(function(){...modify data...}) from both functions? Or, in this scenario would it be necessary to create a new promise (using $q.defer()) and resolve it somehow based on a cache object which holds the HTTP response?
You can simply create two promises, using chaining, on the http promise:
var httpPromise = $http.get('/some/endpoint/');
var firstPromise = httpPromise.then(function(response) {
return firstTransformation(response.data);
});
var secondPromise = httpPromise.then(function(response) {
return secondTransformation(response.data);
});
this.getSomePromise = function() {
return firstPromise;
}
this.getAnotherPromise = function() {
return secondPromise;
}

Preserving scope across routed views

I have a SPA with a list of Clients displayed on the landing page. Each client has an edit button, which if clicked should take me to an Edit view for that selected Client.
I'm not sure how to go about this- all the routes I've seen so far will just take my client id in the $routeParams, and then most examples will then pull the Client from a factory by that Id.
But I already HAVE my Client... seems a waste to hit my web api site again when I already have it. Is it possible to route to the new view and maintain the selected Client in the $scope?
Edit:
This is what I did- I don't know if it's better or worse than Clarks response... I just made the following angular service:
app.service('clientService', function () {
var client = null;
this.getClient = function () {
return client;
};
this.setClient = function (selectedClient) {
client = selectedClient;
};
});
And then for any controller that needs that data:
$scope.client = clientService.getClient();
This seemed to work fine... but would love to hear how this is good or bad.
Depends on what level of caching you want.
You could depend on browser caching, in which case proper HTTP headers will suffice.
You could depend on cache provided by $http in angular, in which case making sure the parameters you send up are the same would be sufficient.
You could also create your own model caching along the lines of :
module.factory('ClientModel', function($http, $cacheFactory, $q){
var cache = $cacheFactory('ClientModel');
return {
get : function(id){
var data = cache.get(id);
if(data){
//Using $q.when to keep the method asynchronous even if data is coming from cache
return $q.when(data);
} else {
//Your service logic here:
var promise = $http.get('/foo/bar', params).then(function(response){
//Your model logic here
var data = response;
cache.put(id, data);
return response;
}, function(response){
cache.remove(id);
return response;
});
//Store the promise so multiple concurrent calls will only make 1 http request
cache.put(id, promise);
return promise;
}
},
clear : function(id){
if(angular.isDefined(id)){
cache.remove(id);
} else {
cache.removeAll();
}
}
}
});
module.controller('ControllerA', function(ClientModel){
ClientModel.get(1).then(function(){
//Do what you want here
});
});
module.controller('ControllerB', function(ClientModel){
ClientModel.get(1).then(function(){
//Do what you want here
});
});
Which would mean each time you request a client object with the same 'id', you would get the same object back.

Resources