multiple method call same single service with cachefactory in angularjs - angularjs

I have a service like below... which always get all types of buyers from server.
var buyersService = function ($http, $q,$cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
BuyersFactory.GetBuyers = function (type) {
var buyersDiffer = $q.defer();
var dataList = buyersService.cache.get('BuyerData');
if (dataList != null && dataList.length > 0) {
buyersDiffer .resolve(_getBuyerByType(type,dataList));
}
else {
$http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
buyersDiffer .resolve(_getBuyerByType(type,results.data));
});
}
return buyersDiffer .promise;
}
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});
function _getBuyerByType(Type,dataList) {
try {
var typedBuyer= Enumerable.From(dataList).Where(function (x) {
return x.Type== Type;
}).ToArray();
return typedBuyer;
} catch (e) {
throw e;
}
}
function getLocalBuyer(){
return BuyersFactory.GetBuyers(1);
}
function getForeignBuyer(){
return BuyersFactory.GetBuyers(2);
}
There are two types of buyer in my business. Such as localBuyer and foreignBuyer. In my controller I need to call two service at a time with Q.All. When two service hit in buyersService method then system call server two times. Hence current cash is empty. But I want that system call server one times for one service call and second service get data from cache.
EDIT: Controller code
function loadDefaultData() {
try {
$q.all([
OMDataService.getLocalBuyer(),
OMDataService.getForeignBuyer(),
]).then(function (data) {
$timeout(function () {
//set dropdown list
viewData.local= data[0];
viewData.foreign= data[1];
}, 0);
}).catch(function (e) {
showError(e);
});
} catch (e) {
showError(e);
}
}

I would suggest defining a variable to save the promise returned by $http. The first time the service is called, that variable is initially undefined. It is set to the promise returned by $http.
The logic of your service thus becomes:
if cache, return cache
if promise, return promise
else call server and save promise
I removed you deferred variable. It is no more needed. I did wrap the cache response in $q.when() (doc: https://docs.angularjs.org/api/ng/service/$q)
var buyersService = function ($http, $q, $cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
var serverPromise; // Will be used to store the promise returned by $http on first server call
BuyersFactory.GetBuyers = function (type) {
var dataList = buyersService.cache.get('BuyerData');
function serverCall () { // stores promise receives by $http and return it
serverPromise = $http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
return _getBuyerByType(type, results.data);
});
return serverPromise;
}
if (dataList !== null && dataList.length > 0) {
return $q.when(_getBuyerByType(type,dataList)); // auto resolving promise
}
// If a first call to the server was already done, serverPromise will exist and no further call to the server will be made
return serverPromise || serverCall();
};
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});
function _getBuyerByType(Type,dataList) {
try {
var typedBuyer= Enumerable.From(dataList).Where(function (x) {
return x.Type== Type;
}).ToArray();
return typedBuyer;
} catch (e) {
throw e;
}
}

Related

Call a asynchronous method after another in AngularJS

I have a $watch attached to the appId variable. SO, when appId get changed it called the init function
$scope.$watch('appId', function() {
if ($scope.appId != '') {
console.log('Inside appId watch');
$scope.init();
}
});
init() calls two service methods
$scope.init = function() {
if ($scope.appId === undefined || $scope.appId == '') return false;
$scope.getPrincipals($scope.loadPrincipals);
$scope.getSignaturesByAppId($scope.loadSignatures);
};
The methods are
$scope.getSignaturesByAppId = function (callback) {
ApplicationDataSource.getSignaturesByAppId($scope.appId, function (result) {
callback(result);
$scope.$apply();
});
};
$scope.loadSignatures = function (result) {
var signatureResultSet = angular.fromJson(result[0]);
$scope.appSignatures = signatureResultSet;
if($scope.appSignatures.length===0){
$scope.setDefaultValue();
}
else{
$scope.setValueFromObject();
}
};
$scope.getPrincipals = function (callback) {
ApplicationDataSource.getApplicationPrincipalList($scope.appId, function (result) {
callback(result);
$scope.$apply();
});
};
$scope.loadPrincipals = function (result) {
var guarantorResultSet = angular.fromJson(result[0]);
$scope.principals = guarantorResultSet;
};
The problem occurs here. In loadSignatures(), I have called a method setDefaultValue() which needs the data retrieve from loadPrincipals. So, when, loadSignatures called, principal data is not updated.
How to call the $scope.getPrincipals($scope.loadPrincipals) after $scope.getSignaturesByAppId($scope.loadSignatures) finish to retrieve data.
You can use Promises, here is an example:
var promise = callThatRunsInBackground();
promise.then(
function(answer) {
// do something
},
function(error) {
// report something
},
function(progress) {
// report progress
});
So in your code it might look like (I will leave it to you to fix as I'm not going to compile or check for syntax errors):
var getPrincipals = $scope.getPrincipals($scope.loadPrincipals);
getPrincipals.then(
function(datapayload) {
//do something with datapayload perhaps
$scope.getSignaturesByAppId($scope.loadSignatures);
});
This will make it wait until getPrincipals is finished before running getSignaturesbyAppId
What solve my issue is, I called $scope.getSignaturesByAppId($scope.loadSignatures); in loadPrincipals callback function, not in init()
$scope.loadPrincipals = function (result) {
var guarantorResultSet = angular.fromJson(result[0]);
$scope.principals = guarantorResultSet;
$scope.getSignaturesByAppId($scope.loadSignatures);
};

Calling second http call after the first http call finish

This is the service where im saving the data and returning the result
nurseService.js
(function () {
'use strict';
angular.module('app.services')
.factory('NurseService', NurseService);
NurseService.$inject = ['$http', '$q','Constants'];
function NurseService($http, $q, Constants){
var service = {
saveSample:saveSample
};
return service;
function saveSample(data) {
var deferred = $q.defer();
$http({method:"POST", data:data, url:Constants.API_URL_SAVE_SAMPLE_COLLECATION}).then(function(result){
return deferred.resolve(result.data);
});
};
return deferred.promise;
}
})();
This is the controller where im using the return value and based on the value returned im calling another http get method and printing it.
vm.saveSamples = function() {
var data = {
visitId: visitId,
orders: vm.gridTestApi.selection.getSelectedRows()
};
var url = Constants.API_URL_SAVE_SAMPLE_COLLECATION;
var barCodeResponse = null;
var sampleId = "";
var myDataPromise = NurseService.saveSample(data);
myDataPromise.then(function(result) {
console.log("data.name"+ JSON.stringify(result));
vm.printBarCode(result.sampleId);
// if(sampleId != ""){
printElement("printThisElement");
// }
});
//Barcode method this should call after saving the data and returned the sampleId
vm.printBarCode = function(sampleId) {
$http.get("master/barcode/"+sampleId).then(function (response) {
vm.barCodeImage = angular.copy(response.data.result);
});
}
But here before the saving print is calling. How can I hadle so that the first call should finish before the second http call to barcode and print it
//Print code
function printElement(elem) {
var printSection = document.getElementById('printSection');
// if there is no printing section, create one
if (!printSection) {
printSection = document.createElement('div');
printSection.id = 'printSection';
document.body.appendChild(printSection);
}
var elemToPrint = document.getElementById(elem);
// clones the element you want to print
var domClone = elemToPrint.cloneNode(true);
printSection.innerHTML = '';
printSection.appendChild(domClone);
window.print();
window.onafterprint = function () {
printSection.innerHTML = '';
}
};
You have to return the $http call in printBarCode and use a .then like so:
//Barcode method this should call after saving the data and returned the sampleId
vm.printBarCode = function(sampleId) {
return $http.get("master/barcode/"+sampleId).then(function (response) {
vm.barCodeImage = response.data.result;
});
}
myDataPromise.then(function(result) {
console.log("data.name"+ JSON.stringify(result));
return vm.printBarCode(result.sampleId)
}).then(
function() {
printElement("printThisElement");
},
function(error) {
// error handler
}
);
printElement will now wait for the printBarCode promise and .then to fulfil before executing.
You also don't have to use a $q.defer when doing a $http call, $http is already a promise so you can just return that like so:
function saveSample(data) {
return $http({method:"POST", data:data, url:Constants.API_URL_SAVE_SAMPLE_COLLECATION})
.then(
function(result) {
return result.data;
},
function(error) {
// don't forget to handle errors
}
);
}
First of all, $http internally implements promises you you dont have to explicitly create them.
Secondly, you should put all your http requests in the service/factory
The modified code looks like
angular.module('app.services')
.factory('NurseService', function($http){
var service = {
saveSample : function(data){
//first http implementation here
return $http.post(....);
}
getBarcode : function(sampleId){
//http implementation here for barcode
return $http.get(....);
}
}
return service;
});
and your controller can use the service like
angular.module('app.services')
.controller('saveSampleCtrl',function($scope,NurseService){
var postData = {
//your post data here
}
NurseService.saveSample(postData)
.then(function(data){
//read sampleId here from data
var sampleId = data.sampleId;
NurseService.getBarcode(sampleId)
.then(function(){
//your print statement here
});
});
}
there might be typos in the code but this is just a basic idea on how you could do that. Hope it helps

Reuse data from $http in factory

I understand that the appropriate method to share data between controllers in Angular.js is by using Factories or Services.
app.controller('Controller1', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.controller('Controller2', function($scope, DataService) {
DataService.getValues().then(
function(results) {
// on success
console.log('getValues onsuccess');
});
});
app.factory('DataService', function($http) {
var getValues = function() {
console.log('making http request');
return $http.get("/api/getValues");
};
return {
getValues: getValues
}
});
I have two controllers calling the same method in a factory twice
and this is perfectly fine and everything is working as it should. My only concer is that it seems a bit unecessary to make the same request twice? Would the use of $broadcast be a better approach?
Or could i structure my code differenty so that the service is called only once?
You could store the results of the request in the factory and retrieve those instead.
app.factory('DataService', function($http) {
var values;
var requestValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results){
values = results;
});
};
var getValues = function() {
return values;
};
return {
requestValues : requestValues,
getValues: getValues
}
});
If your data is somekind of static and may not change very often over time you could do something like:
app.factory('DataService', function($http) {
self = this;
this.isLoaded = false;
this.results;
this.getValues = function() {
console.log('making http request');
$http.get("/api/getValues").then(
function(results) {
// on success
console.log('getValues onsuccess');
self.isLoaded = true
this.results = results;
return results;
})
);
};
})
And in the controller:
app.controller('Controller2', function($scope, DataService) {
if(!DataService.isLoaded){
results = DataService.getValues()
}else{
results = DataService.results;
}
});
You should consider caching in your DataService. Add a variable to hold the result from the http service and a time-stamp variable to store the time it was retrieved.
If a second call to the service is within a preset time period (lets say, 5 seconds), then http call is not made and data from the cache is returned.
app.factory('DataService', function($http) {
var cachedValue = null;
var lastGet = null;
var getValues = function() {
var timeNow = new Date();
if (cachedValue == null || ((timeNow - lastGet) < 5000)) {
console.log('making http request');
lastGet = timeNow;
cachedValue = $http.get("/api/getValues");
} else console.log('returning cached value');
return cachedValue;
};
return {
getValues: getValues
}
});

How to get data by service and $cacheFactory by one method

I have a factory which get data from server. In the factory method I have used $cacheFactory to cache getting data. My code is as follows..
var buyersService = function ($http, $q,$cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
BuyersFactory.GetBuyers = function () {
var dataList = buyersService.cache.get('BuyerData');
if (dataList != null && dataList.length > 0) {
return dataList;
}
else {
return $http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
return results.data;
});
}
}
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});
Now I have called GetBuyers method from controller. My method is like below..
var BuyerController = function ($scope, BuyersService) {
$scope.Buyers = [];
init();
function init() {
getBuyers();
}
function getBuyers() {
BuyersService.GetBuyers()
.then(function (data) {
$scope.Buyers = data;
}, function (error) {
alert(error.message);
});
}
};
app.register.controller('BuyersController', ['$scope', 'OMDataService', BuyerController]);
When I have executed my controller method second time I have got an error message in promise part.
Object doesn't support property or method 'then'
The issue here is that your function returns two different things: either a promise or plain data. To remedy this, use another promise to control the flow and return that one as the result of the function.
Update your code to
var buyersService = function ($http, $q,$cacheFactory) {
var serviceBase = '/api/OMData/';
var BuyersFactory = {};
buyersService.cache = $cacheFactory('cacheId');
BuyersFactory.GetBuyers = function () {
var buyersDataIsAvailable = $q.defer();
var dataList = buyersService.cache.get('BuyerData');
if (dataList != null && dataList.length > 0) {
buyersDataIsAvailable.resolve(dataList);
}
else {
$http.get(serviceBase + 'GetBuyers').then(
function (results) {
buyersService.cache.put("BuyerData", results.data);
buyersDataIsAvailable.resolve(results.data);
});
}
return buyersDataIsAvailable.promise;
}
app.factory('OMDataService', ['$http', '$q', '$cacheFactory', buyersService]);
});

Angular: Rewriting function to use promise

I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});

Resources