Limit http calls from a factory service - angularjs

I'm using a factory service to fetch some data using the $http service. The problem here, I dont want to make a http request each time, I want to save this data somewhere and get a local copy of it when needed. To that end I thought creating an array inside that factory and assign the loaded data to it on the first call, and then just return it when required, instead of loading it again from the server. In my case, it the http service is fired every time. How can I fix this? I read here but that does not answer my question.
This is my factory:
angular.module("app").factory("getDataService", ['$http', function ($http) {
var usersArray = [];
if (usersArray.length === 0) {
return {
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3')
.success(function (data, status, headers, config) {
usersArray = data;
return data;
})
.error(function (error, status, headers, config) {
});
}
}
}else{
return usersArray;
}
}]);
And this is the controller that uses this service:
angular.module("app").controller("ctrl", ["$scope", "getDataService", function ($scope, getDataService) {
angular.element(document).ready(function () {
getDataService.getJsonData().then(function (data) {
$scope.users = data.data;
});
});
}]);

You do not need to cache the response of $http.get manually, angularJS itself provides a way to cache the response. Try below code in your getJsonData function of your factory:
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3', {cache: true})
.success(function (data, status, headers, config) {
return data;
})
.error(function (error, status, headers, config) {
});
}
Source: https://docs.angularjs.org/api/ng/service/$http#get
Read the above document. You will find configurations from there.

You can use Local Storage for it, one of the best and easiest ways.
LocalStorage.setItem('usersArray',data); sets the data in the local storage.
LocalStorage.getItem('usersArray'); retrieves the data from local storage.
Here is the change of your factory,
angular.module("app").factory("getDataService", ['$http', function ($http) {
var usersArray = LocalStorage.getItem('usersArray');
if (usersArray.length === 0) {
return {
getJsonData: function () {
return $http.get('https://api.myjson.com/bins/eznv3', {cache: true})
.success(function (data, status, headers, config) {
usersArray = data;
LocalStorage.setItem('usersArray',data);
return data;
})
.error(function (error, status, headers, config) {
});
}
}
}else{
return LocalStorage.getItem('usersArray');
}
}]);
Your controller,
angular.module("app").controller("ctrl", ["$scope", "getDataService", function ($scope, getDataService) {
var x = [];
angular.element(document).ready(function () {
if (x.length == 0) {
getDataService.getJsonData().then(function (data) {
x = data.data;
$scope.users = x;
});
}else{
console.log("local copy of data exists");
}
});
}]);
Advantages of localstorage:
With local storage, web applications can store data locally within the user's browser.
Unlike cookies, the storage limit is far larger (at least 5MB) and information is never transferred to the server.

Few days back, i got same kind of requirement and following is code of module i had created for same...
'use strict';
(function() {
angular.module('httpService', []).service("api", ["$http", "dbService", function($http, dbService) {
/**
* <Pankaj Badukale>
* ()
* request.url => Url to request
* request.method => request method
* request.data => request data
* request.mask => This is custom object for out use
*
* #return ()
*/
return function (request) {
var url = (request != undefined && request.url != undefined) ? request.url : "./";
var method = (request != undefined && request.method != undefined) ? request.method : "GET";
var rData = (request != undefined && request.data != undefined) ? request.data : {};
/**
* mask is CUSTOME object we add to request object
* Which is useful for keep track of each request as well interceptor execute
*
* IT HAS
* {
* save : true, //tell that save request response in session
* fetch : true, //check local data first,
* fetchSource : tell about perticular source of data DEFAULT WILL BE sessionStorage
* OPTIONS are session and local
* } strucutre FOR NOW may be it will better or enhance in future
*
* message property to set message in alert
* doExecute tell wheather you want to execute maskMan code for this request
*
* while saving and fetching data from local it uses URL of request as key
* maskMan is a factory which iterate your error response object and we can add different behaviours for maskMan
*/
var mask = {};
if(request != undefined && request.mask != undefined) {
mask = request.mask;
}
return dbService.http(request).then(function(data) {
console.log("Data fetched from local "+ request.url);
return data;
}, function(err) {
return $http({
url: url,
method: method,
data: rData,
mask: mask,
header:{
'content-type':'application/json'
}
}).then(function(response) {
return response.data;
},function(error) {
return error;
});
});
};
}]).service('customHttpInterceptor', ["$q", "maskMan", function($q, maskMan) {
return {
//before send request to server
request: function(config) {
return config;
},
//if any found in request object
requestError: function(rejection) {
return $q.reject(rejection);
},
//on response come to web app
response: function(response) {
maskMan.responseIterator(response);
//you to return any thing as response from here
return response;
},
//if there is error in response`
responseError: function(rejection) {
maskMan.statusIterator(rejection);
return $q.reject(rejection);
}
};
}]).factory("maskMan", ["dbService", function(dbService) {
return {
/**
* statusIterator
* Iterate response object on error comes
*/
statusIterator: function(rejection) {
if( rejection.config.mask.doExecute == true) {
switch(rejection.status) {
case 404: this.notFound(rejection);
break;
default: this.dontKnow(rejection);
}
}
},
/**
* notFound
* Function to defined logic for 404 error code scenario's
* Here we can defined generic as well specific request object conditions also
*/
notFound: function(rejection) {
var errMsg = rejection.config.mask.message || "Something wrong";
alert(errMsg);
rejection.stopExecute = true;//stop further execute of code flag
},
/**
* dontKnow
* For every error response this method goingt to envoke by default
*/
dontKnow: function(maskObject) {
console.log("Don't know what to do for "+maskObject.config.url);
},
/**
* responseIterator
* Define logic to do after response come to browser
*
* #params JSON resp
*/
responseIterator: function(resp) {
//Logic to save data of response in session storage with mask command save
if( resp.config.mask !== undefined && resp.config.mask.save === true ) {
var sdata = JSON.stringify(resp.data);
var skey = resp.config.url;
dbService.sinsert(skey, sdata);
}//END
}
};
}]).service("dbService", ["$q", function($q) {
/**
* http
* Custom mirror promise to handle local storage options with http
*
* #params JSON request
*/
this.http = function(request) {
var self = this;
return $q(function(resolve, reject) {
if( request.mask != undefined && request.mask.fetch === true ) {
var data = null;
if( request.mask.fetchSource == undefined || request.mask.fetchSource == "session") {//go for default sessionStorage
data = JSON.parse(self.sget(request.url));
} else if( request.mask.fetchSource == "local" ) {
data = JSON.parse(self.get(request.url));
} else {
reject( "Fetch source is not defined." );
}
if( data != undefined && data != null ) {
resolve(data);
} else {
reject("Data not saved in local "+request.url);
}
} else {
reject("Data not saved in local "+request.url);
}
});
}
/**
* Add/Override data to local storage
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.insert = function(key, data, callback) {
localStorage.setItem(key, data);
if( callback != undefined ) {
callback();
} else {
return true;
}
}
/**
* Update data of local storage
* This function generally used to data which is already exist and need to update
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.update = function(key, data, callback) {
var self = this;
self.view(key, function(localData) {//callback function
if( localData != undefined && localData != null ) {
//already some data exist on this key So need to update it
data = localData.push(data);
}
//just handover to insert
if( callback !== undefined ) {
self.insert(key, data, callback);
} else {
return self.insert(key, data);
}
});
}
/**
* Remove data from local storage on basis of key
*
* #params String key
* #return Boolean
*/
this.remove = function(key, callback) {
localStorage.removeItem(key);
if( callback !== undefined ) {
callback();
} else {
return true;
}
}
/**
* Get key data of local storage
* #param String key
*
* #return Array data WHEN all data OR
* #return String data WHEN key value
*/
this.get = function(key, callback) {
var key = key || "";
var data = [];
if( key == "" ) {
//get all data
for(var i in localStorage) {
data.push(JSON.parse(localStorage[i]));
}
} else {
//get one key data
data = localStorage.getItem(key);
}
if(callback != undefined) {
callback(data);
} else {
return data;
}
}
/**
* sinsert
* Add/Override data to session storage
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.sinsert = function(key, data, callback) {
var key = this.encode(key);
sessionStorage.setItem(key, data);
if( callback != undefined ) {
callback();
} else {
return true;
}
}
/**
* supdate
* Update data of session storage
* This function generally used to data which is already exist and need to update
*
* #params String key
* #params Array/Json data
* #params Function callback
*
* #return Boolean/Function
*/
this.supdate = function(key, data, callback) {
var self = this;
self.view(key, function(localData) {//callback function
if( localData != undefined && localData != null ) {
//already some data exist on this key So need to update it
data = localData.push(data);
}
//just handover to insert
if( callback !== undefined ) {
self.insert(key, data, callback);
} else {
return self.insert(key, data);
}
});
}
/**
* sremove
* Remove data from session storage on basis of key
*
* #params String key
* #return Boolean
*/
this.sremove = function(key, callback) {
var key = this.encode(key);
sessionStorage.removeItem(key);
if( callback !== undefined ) {
callback();
} else {
return true;
}
}
/**
* get
* Get key data of session storage
* #param String key
*
* #return Array data WHEN all data OR
* #return String data WHEN key value
*/
this.sget = function(key, callback) {
var key = key || "";
var data = [];
if( key == "" ) {
//get all data
for(var i in sessionStorage) {
data.push(JSON.parse(sessionStorage[i]));
}
} else {
//get one key data
key = this.encode(key);
data = sessionStorage.getItem(key);
}
if(callback != undefined) {
callback(data);
} else {
return data;
}
}
/**
* encode
* encode give string using javascript
*
* #param String str
* #return String
*/
this.encode = function(str) {
return btoa(str);
}
/**
* decode
* decode give string using javascript
*
* #param String str
* #return String
*/
this.decode = function(str) {
return atob(str);
}
return this;
}]).config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('customHttpInterceptor');
}]);
})();
How to use it::
Include this module in you project....
Then use "httpService" always for http requests all for API calls...
We need to pass config object to this service tell about API call and what should do with it....You can find details about config in code itself...
So how to use in controller..
module.controller('nameofController', ['httpService', function(httpService) {
httpService({
url: 'Your API url',
method: 'GET',
mask: {
save : true, //tell that save request response in session
fetch : true, //check local data first before next fetch,
fetchSource : tell about perticular source of data DEFAULT WILL BE sessionStorage OPTIONS are session and local
}
}).then(function(data) {
// promise is all same as $http
console.log(data);
});
}]);
Hope this will help... You can go with very simple solution as well to just mark
{cache: true}
...
But this solution is completely customized and under all controls
Original code which has used in production is at gist

Related

Passing params: to Web API works with $http.get but not $http.Post

AngularJS 1.59
This API call works with $http.get.
JS ViewModel
$scope.placeOrder = function () { //'api/order/create'
var order = { AccountId : accountId, Amount : $scope.subTotal,
Tax: $scope.tax, Shipping: $scope.shipping }
var orderJSON = JSON.stringify(order);
viewModelHelper.apiGet('api/order/create', { params: { order: orderJSON } },
function (result) {
var orderId = result.data;
});
}
App.js
self.apiGet = function (uri, data, success, failure, always) {
self.isLoading = true;
self.modelIsValid = true;
$http.get(AlbumApp.rootPath + uri, data)
.then(function (result) {
success(result);
if (always != null)
always();
self.isLoading = false;
}, function (result) {
if (failure == null) {
if (result.status != 400)
self.modelErrors = [result.status + ': ' + result.statusText +
' - ' + result.data];
else
self.modelErrors = [result.data + ''];
self.modelIsValid = false;
}
else
failure(result);
if (always != null)
always();
self.isLoading = false;
});
}
self.apiPost = function (uri, data, success, failure, always) {
self.isLoading = true;
self.modelIsValid = true;
$http.post(AlbumApp.rootPath + uri, data)
.then(function (result) {
success(result);
if (always != null)
always();
self.isLoading = false;
}, function (result) {
if (failure == null) {
if (result.status != 400)
self.modelErrors = [result.status + ': ' + result.statusText + ' - ' + result.data];
else self.modelErrors = [result.data];
self.modelIsValid = false;
}
else failure(result);
if (always != null) always();
self.isLoading = false;
});
}
APIController
[HttpGet]
[Route("create")]
public IHttpActionResult Create(string order) {
var _order = JsonConvert.DeserializeObject<Order>(order); ... }
But since this is a Create function I want to use $http.post. When I change the call to
$scope.placeOrder = function () { //'api/order/create'
var order = { AccountId : accountId, Amount : $scope.subTotal,
Tax: $scope.tax, Shipping: $scope.shipping }
var orderJSON = JSON.stringify(order);
viewModelHelper.apiPost('api/order/create', { params: { order: orderJSON } },
//null,
function (result) {
var orderId = result.data;
});
}
and my controller Action to
[HttpPost]
[Route("create")]
public IHttpActionResult Create(string order) {
var _order = JsonConvert.DeserializeObject<Order>(order); ... }
I get a 404 error:
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:50597/api/order/create'.
</Message>
<MessageDetail>
No action was found on the controller 'OrderApi' that matches the request.
</MessageDetail>
</Error>
Is this a bug or am I missing some conceptual point or do I have an error in my code?
Solution: (Thank you Giovani)
params: needs to be passed to the config in the $http.get and $http.post. The two methods have different signatures.
In apiGet renamed data to config.
In apiPost added a config.
In apiPost call added a null so the params: is passed to config rather than data.
App.js
self.apiGet = function (uri, config, success, failure, always) {
self.isLoading = true;
self.modelIsValid = true;
$http.get(AlbumApp.rootPath + uri, config)
...
self.apiPost = function (uri, data, config, success, failure, always) {
self.isLoading = true;
self.modelIsValid = true;
$http.post(AlbumApp.rootPath + uri, data, config)
JS ViewModel
$scope.placeOrder = function () { //'api/order/create'
var order = { AccountId : accountId, Amount : $scope.subTotal,
Tax: $scope.tax, Shipping: $scope.shipping }
var orderJSON = JSON.stringify(order);
viewModelHelper.apiPost('api/order/create', null, { params: { order: orderJSON } },
function (result) {
var orderId = result.data;
}); }
$http.get() and $http.post() have a different method signature. more info
$http.get(<URL>, <DATA (params, responseType, etc..)>)
$http.post(<URL>, <BODY_DATA>, <DATA (params, responseType, etc..)>

dealing with an array of objects with promises

I am trying to make a node express app where I fetch data from different url's making a call to node-fetch to pull the body of some pages and other information about certain url endpoints. I want to then render a html table to display this data through an array of information. I am having trouble with the call to render the information as all the functions are asynchronous making it difficult to make sure all the promise calls have been resolved before making my call to render the page. I have been looking into using bluebird and other promise calls of .finally() and .all() but they don't seem to work on my data as it is not an array of promise calls, but an array of objects. Each object was 4 promise calls to fetch data relating to a column of my table all in one row. Is there a function or specific way to render the page after all promises are resolved?
var express = require('express');
var fetch = require('node-fetch');
fetch.Promise = require('bluebird');
var router = express.Router();
const client = require('../platform-support-tools');
function makeArray() {
var registry = client.getDirectory();
var data_arr = [];
for (var i = 0; i < registry.length; i++) {
var firstUp = 0;
for (var j = 0; i < registry[i]; j++) {
if (registry[i][j]['status'] == 'UP') {
firstUp = j;
break;
}
}
var object = registry[i][firstUp];
data_arr.push({
'name': object['app'],
'status': object['status'],
'swagUrl': object['homePageUrl'] + 'swagger-ui.html',
'swag': getSwag(object),
'version': getVersion(object['statusPageUrl']),
'timestamp': getTimestamp(object['statusPageUrl']),
'description': getDescription(object['healthCheckUrl'])
});
}
return data_arr;
}
function getSwag(object_in) {
var homeUrl = object_in['homePageUrl'];
if (homeUrl[homeUrl.length - 1] != '/'){
homeUrl += '/';
}
var datum = fetch(homeUrl + 'swagger-ui.html')
.then(function (res) {
return res.ok;
}).catch(function (err) {
return 'none';
});
return datum;
}
function getVersion(url_in) {
var version = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return version;
}
function getTimestamp(url_in) {
var timestamp = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['timestamp'];
}).then(function (res) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return timestamp;
}
function getDescription(url_in) {
var des = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['description'];
}).catch(function (error) {
return 'none';
});
return des;
}
/* GET home page. */
router.get('/', function (req, res, next) {
var data_arr = makeArray();
Promise.all(data_arr)
.then(function (response) {
//sorting by app name alphabetically
response.sort(function (a, b) {
return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
});
res.render('registry', {title: 'Service Registry', arr: response})
}).catch(function (err) {
console.log('There was an error loading the page: '+err);
});
});
To wait on all those promises, you will have to put them into an array so you can use Promise.all() on them. You can do that like this:
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// all promises done here
})
If you want the values from all those promises, back into the object that's a bit more work.
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// replace promises with their resolved values
let index = 0;
for (let i = 0; i < results.length; i += 4) {
data_arr[index].swag = results[i];
data_arr[index].version = results[i + 1];
data_arr[index].timestamp = results[i + 2];
data_arr[index].description = results[i + 3];
++index;
});
return data_arr;
}).then(function(data_arr) {
// process results here in the array of objects
});
If you had to do this more often that just this once, you could remove the hard coding of property names and could iterate all the properties, collect the property names that contain promises and automatically process just those.
And, here's a more general version that takes an array of objects where some properties on the objects are promises. This implementation modifies the promise properties on the objects in place (it does not copy the array of the objects).
function promiseAllProps(arrayOfObjects) {
let datum = [];
let promises = [];
arrayOfObjects.forEach(function(obj, index) {
Object.keys(obj).forEach(function(prop) {
let val = obj[prop];
// if it smells like a promise, lets track it
if (val && val.then) {
promises.push(val);
// and keep track of where it came from
datum.push({obj: obj, prop: prop});
}
});
});
return Promise.all(promises).then(function(results) {
// now put all the results back in original arrayOfObjects in place of the promises
// so now instead of promises, the actaul values are there
results.forEach(function(val, index) {
// get the info for this index
let info = datum[index];
// use that info to know which object and which property this value belongs to
info.obj[info.prop] = val;
});
// make resolved value be our original (now modified) array of objects
return arrayOfObjects;
});
}
You would use this like this:
// data_arr is array of objects where some properties are promises
promiseAllProps(data_arr).then(function(r) {
// r is a modified data_arr where all promises in the
// array of objects were replaced with their resolved values
}).catch(function(err) {
// handle error
});
Using the Bluebird promise library, you can make use of both Promise.map() and Promise.props() and the above function would simply be this:
function promiseAllProps(arrayOfObjects) {
return Promise.map(arrayOfObjects, function(obj) {
return Promise.props(obj);
});
}
Promise.props() iterates an object to find all properties that have promises as values and uses Promise.all() to await all those promises and it returns a new object with all the original properties, but the promises replaced by the resolved values. Since we have an array of objects, we use Promise.map() to iterate and await the whole array of those.

Call multiple web api using Angular js one by one

I have a scenario in which there is 8 web api's called as :
#1
Sync Local DB from server DB (response will RETURN a List=> myList)
If (myList.Length > 0)
#1.1 Call web Api to Insert/Update Local DB
#2
Sync Server DB from Local DB (Request goes with a List=> myList)
If (myList.Length > 0)
#2.1 Call web Api to Insert/Update in Server DB (Response will RETURN a List=> newList)
If(newList.length > 0)
#2.2 Call web Api to Insert/Update in Local DB
I have two separate process For Head and Head Collection tables which synced with above process. So there is #3 and #4 scenario is also present.
I have call web api in the following manner...
syncHeadDataLogic();
syncHeadCollectionDataLogic();
I need that Head data should be synced first then HeadCollection data synced. But if there is no updated record for head then Head collection executed.
In my scenario my web apis called in any order but i need a order as I have described above. Kindly suggest me how I achieved this.
#Updated
//Sync Head
$scope.initializeController = function () {
if ($scope.online) {
//debugger;
syncHeadDataLogic();
syncHeadCollectionDataLogic();
}
};
function syncHeadDataLogic() {
HeadService.HeadSyncLocalDB(parseInt(localStorage.headRevision, 10), $scope.completeds, $scope.erroe);
};
$scope.SynServerDBCompleted = function (response) {
debugger;
$scope.HeadListForSync = response.HeadList;
var tempHeadCurrencyDetail = [];
if ($scope.HeadListForSync.length > 0) {
angular.forEach($scope.HeadListForSync, function (xx) {
xx.CurrencyId = xx.CurrencyServerId;
xx.Id = xx.HeadServerId;
angular.forEach(xx.HeadCurrencyDetail, function (yy) {
yy.CurrencyId = yy.CurrencyServerId;
yy.HeadId = xx.HeadServerId;
if (yy.Revision == -1)
tempHeadCurrencyDetail.push(yy);
});
xx.HeadCurrencyDetail = tempHeadCurrencyDetail;
});
var postData = { Revision: parseInt(localStorage.headRevision, 10), HeadList: $scope.HeadListForSync };
HeadService.SynServerDB(postData, $scope.completed, $scope.erroe);
}
else {
// alertsService.RenderSuccessMessage("There is no change in data after your last synchronization.");
}
};
$scope.requestErrorwer = function (response) {
debugger;
};
$scope.completed = function (response) {
debugger;
if (response.RevisionNo == localStorage.headRevision) {
syncHeadCollectionDataLogic();
// alertsService.RenderErrorMessage("There is newer version on the server. Please Sync from server first.", "MessageAlert");
}
else {
syncData(response);
}
};
$scope.completeds = function (response) {
debugger;
if (response.RevisionNo == localStorage.headRevision) {
syncHeadCollectionDataLogic();
// alertsService.RenderSuccessMessage("You are already working on the latest version", "MessageAlert");
}
else {
syncData(response);
}
//
var request = new Object();
HeadService.getAllHeadForRevision(request, $scope.SynServerDBCompleted, $scope.requestErrorwer);
};
$scope.erroe = function (response) {
debugger;
// alertsService.RenderErrorMessage("Data Synchronization Failed", "MessageAlert");
};
function syncData(data) {
debugger;
$scope.ReturnedRevisonNo = data.RevisionNo;
if (data.HeadList && data.HeadList.length > 0) {
var postData = { Revision: $scope.ReturnedRevisonNo, HeadList: data.HeadList, HeadRevision: $scope.ReturnedRevisonNo };
HeadService.AddUpdateHeadAfterSync(postData, $scope.cmpSync, $scope.Error);
}
else {
syncHeadCollectionDataLogic();
}
};
$scope.cmpSync = function (response) {
debugger;
localStorage.headRevision = $scope.ReturnedRevisonNo;;
alertsService.RenderSuccessMessage("The synchronization has been completed successfully.");
syncHeadCollectionDataLogic();
};
$scope.Error = function (response) {
debugger;
// alertsService.RenderErrorMessage(response.ReturnMessage);
// alertsService.SetValidationErrors($scope, response.ValidationErrors);
};
////////////Sync End
//Sync Head Collection
function syncHeadCollectionDataLogic() {
HeadService.HeadSyncLocalCollectionDB(parseInt(localStorage.headCollectionRevision, 10), $scope.completedCollections, $scope.erroeCollection);
};
$scope.SynServerDBCompletedCollection = function (response) {
$scope.HeadCollectionListForSync = response.HeadCollectionList;
if ($scope.HeadCollectionListForSync.length > 0) {
angular.forEach($scope.HeadCollectionListForSync, function (value, index) {
value.Id = value.HeadCollectionServerId;
angular.forEach(value.HeadCollectionDetails, function (v) {
v.CommittedCurrencyId = v.CommittedCurrencyServerId;
v.HeadId = v.HeadServerId;
v.WeightId = v.WeightServerId;
v.HeadCollectionId = value.HeadCollectionServerId; //change
angular.forEach(v.HeadCollectionAmountDetails, function (xx) {
xx.CurrencyId = xx.CurrencyServerId;
});
});
});
var postData = { Revision: parseInt(localStorage.headCollectionRevision, 10), HeadCollectionList: $scope.HeadCollectionListForSync };
HeadService.SynServerCollectionDB(postData, $scope.completedCollection, $scope.erroeCollection);
}
else {
// alertsService.RenderSuccessMessage("There is no change in data after your last synchronization.");
}
};
$scope.requestErrorwerCollection = function (response) {
};
$scope.completedCollection = function (response) {
if (response.RevisionNo == localStorage.headCollectionRevision) {
// alertsService.RenderErrorMessage("There is newer version on the server. Please Sync from server first.", "MessageAlert");
}
else {
syncDataCollection(response);
}
};
$scope.completedCollections = function (response) {
if (response.RevisionNo == localStorage.headCollectionRevision) {
// alertsService.RenderSuccessMessage("You are already working on the latest version", "MessageAlert");
}
else {
syncDataCollection(response);
}
var request = new Object();
HeadService.getAllHeadCollectionForRevision(request, $scope.SynServerDBCompletedCollection, $scope.requestErrorwerCollection);
};
$scope.erroeCollection = function (response) {
// alertsService.RenderErrorMessage("Data Synchronization Failed", "MessageAlert");
};
function syncDataCollection(data) {
$scope.ReturnedRevisonNo = data.RevisionNo;
if (data.HeadCollectionList && data.HeadCollectionList.length > 0) {
var postData = { Revision: $scope.ReturnedRevisonNo, HeadCollectionList: data.HeadCollectionList, HeadRevision: $scope.ReturnedRevisonNo };
HeadService.AddUpdateaHeadCollectionAfterSync(postData, $scope.cmpSyncCollection, $scope.ErrorCollection);
}
};
$scope.cmpSyncCollection = function (response) {
localStorage.headCollectionRevision = $scope.ReturnedRevisonNo;;
alertsService.RenderSuccessMessage("The synchronization has been completed successfully.");
$scope.initializeController();
};
$scope.ErrorCollection = function (response) {
// alertsService.RenderErrorMessage(response.ReturnMessage);
// alertsService.SetValidationErrors($scope, response.ValidationErrors);
}
//End
I need that Head data should be synced first then HeadCollection data synced. But if there is no updated record for head then Head collection executed.
What you need is chained promises. Try this (I'm giving you pseudocode for now):
HeadService.HeadData
|-----------------|
HeadCollection(headDataResult)
|------------------|
finalHandler(headCollectionResult)
|------------------|
HeadService.HeadData()
.then(HeadService.HeadCollection) // return or throw Err if headDataResult is empty
.then(finalHandler);
Here, the order of execution of the promises will be predictable, and sequential. Also, each promise will be returned the resolved value of the previous promise
AngularJS as you can see in the documentation here, uses Promises out of the box with the $http injectable. You can define a factory like so:
// Factory code
.factory("SampleFactory", function SampleFactory($http) {
var sampleFactoryObject = {};
sampleFactoryObject.getSomething = function() {
return $http.get('/someUrl');
}
sampleFactoryObject.getSomething.then(function resolveHandler(res) {
return res;
},
function rejectHandler(err) {
throw new Error(err);
});
sampleFactoryObject.getSomethingElse = function() {
return $http.get('/someOtherUrl');
}
sampleFactoryObject.getSomethingElse.then(function resolveHandler(res) {
return res;
},
function rejectHandler(err) {
throw new Error(err);
});
return sampleFactoryObject;
});
// Controller code
.controller('myController', function myController(SampleFactory) {
SampleFactory.getSomething()
.then(SampleFactory.getSomethingElse())
.then(finalHandler);
var finalHandler = function(resultOfGetSomethingElse) {
console.log(resultOfGetSomethingElse);
}
});

Prevent multiple submits in angularjs

I'm looking for a AngularJS-based way to prevent multiple submits per task.
I don't need buttons to be disabled after submission or close the form and wait for the task to be completed. Instead, I need requests to be unique.
To be more detailed, I need $http.get and $http.post stop sending multiple same requests.
Any Ideas?
According to this article, you can use provider decorator.
NOTE: this approach is based on angular-api
https://gist.github.com/adambuczynski/354364e2a58786e2be71
UPDATE
I've changed a little part in your suggested solution, because returned promises have lost .success and .error and .then.
Just use this edited code to have all of those functions working:
.config(["$provide", function ($provide) {
$provide.decorator('$http', function ($delegate, $q) {
var pendingRequests = {};
var $http = $delegate;
function hash(str) {
var h = 0;
var strlen = str.length;
if (strlen === 0) {
return h;
}
for (var i = 0, n; i < strlen; ++i) {
n = str.charCodeAt(i);
h = ((h << 5) - h) + n;
h = h & h;
}
return h >>> 0;
}
function getRequestIdentifier(config) {
var str = config.method + config.url;
if (config.data && typeof config.data === 'object') {
str += angular.toJson(config.data);
}
return hash(str);
}
var $duplicateRequestsFilter = function (config) {
if (config.ignoreDuplicateRequest) {
return $http(config);
}
var identifier = getRequestIdentifier(config);
if (pendingRequests[identifier]) {
if (config.rejectDuplicateRequest) {
return $q.reject({
data: '',
headers: {},
status: config.rejectDuplicateStatusCode || 400,
config: config
});
}
return pendingRequests[identifier];
}
pendingRequests[identifier] = $http(config);
$http(config).finally(function () {
delete pendingRequests[identifier];
});
return pendingRequests[identifier];
};
Object.keys($http).filter(function (key) {
return (typeof $http[key] === 'function');
}).forEach(function (key) {
$duplicateRequestsFilter[key] = $http[key];
});
return $duplicateRequestsFilter;
})
}])
It could be a performance issue but following idea could solve your problem.
Store the each request URL and DATA as key value pair on a variable. URL should be KEY. For Same URL multiple submission can be stored in a Array.
Then for any new call check the URL if it present in your stored object, then compare the data with each object thorughly (deep check, it is costly though).
If any exact match found then stop the processing. As same request came.
Other wise proceed and don't forget to store this data also.
But it is costly since need to check the data which could be havy.
Note: At the time of storing the data you could convert it to JSON String so it will be easier to compare between String.
here is the Code Algo
YourService.call(url, params) {
var Str1 = JSON.stringify(params);
if(StoredObj[url]) {
for each (StoredObj[url] as Str){
if(Str === Str1) {
return;
}
}
}
else {
StoredObj[url] = []; //new Array
}
StoredObj[url].push(Str1);
Call $http then;
}

Is [] parameter in the AngularJS module definition compulsory?

In definition of AngularJS module, [] is a parameter for other depended module on this module.
<script>
var app = angular.module('myApp',[]);
app.controller("myCtrl", function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
My Question is,
Is this parameter [] necessary, because the the following link or example they didn't mentioned [] parameter, but in above example(w3schooles), if we remove '[]' parameter then code will not give correct output see it?
Please see the this link openstack, they are not using [] parameter
var module = angular.module('hz.dashboard.launch-instance');
/**
* #ngdoc service
* #name launchInstanceModel
*
* #description
* This is the M part in MVC design pattern for launch instance
* wizard workflow. It is responsible for providing data to the
* view of each step in launch instance workflow and collecting
* user's input from view for creation of new instance. It is
* also the center point of communication between launch instance
* UI and services API.
*/
module.factory('launchInstanceModel', ['$q',
'cinderAPI',
'glanceAPI',
'keystoneAPI',
'neutronAPI',
'novaAPI',
'novaExtensions',
'securityGroup',
'serviceCatalog',
function ($q,
cinderAPI,
glanceAPI,
keystoneAPI,
neutronAPI,
novaAPI,
novaExtensions,
securityGroup,
serviceCatalog) {
var initPromise,
allNamespacesPromise;
// Constants (const in ES6)
var NON_BOOTABLE_IMAGE_TYPES = ['aki', 'ari'],
SOURCE_TYPE_IMAGE = 'image',
SOURCE_TYPE_SNAPSHOT = 'snapshot',
SOURCE_TYPE_VOLUME = 'volume',
SOURCE_TYPE_VOLUME_SNAPSHOT = 'volume_snapshot';
/**
* #ngdoc model api object
*/
var model = {
initializing: false,
initialized: false,
/**
* #name newInstanceSpec
*
* #description
* A dictionary like object containing specification collected from user's
* input. Its required properties include:
*
* #property {String} name: The new server name.
* #property {String} source_type: The type of source
* Valid options: (image | snapshot | volume | volume_snapshot)
* #property {String} source_id: The ID of the image / volume to use.
* #property {String} flavor_id: The ID of the flavor to use.
*
* Other parameters are accepted as per the underlying novaclient:
* - https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/servers.py#L417
* But may be required additional values as per nova:
* - https://github.com/openstack/horizon/blob/master/openstack_dashboard/api/rest/nova.py#L127
*
* The JS code only needs to set the values below as they are made.
* The createInstance function will map them appropriately.
*/
// see initializeNewInstanceSpec
newInstanceSpec: {},
/**
* cloud service properties, they should be READ-ONLY to all UI controllers
*/
availabilityZones: [],
flavors: [],
allowedBootSources: [],
images: [],
allowCreateVolumeFromImage: false,
arePortProfilesSupported: false,
imageSnapshots: [],
keypairs: [],
metadataDefs: {
flavor: null,
image: null,
volume: null
},
networks: [],
neutronEnabled: false,
novaLimits: {},
profiles: [],
securityGroups: [],
volumeBootable: false,
volumes: [],
volumeSnapshots: [],
/**
* api methods for UI controllers
*/
initialize: initialize,
createInstance: createInstance
};
// Local function.
function initializeNewInstanceSpec(){
model.newInstanceSpec = {
availability_zone: null,
admin_pass: null,
config_drive: false,
user_data: '', // REQUIRED Server Key. Null allowed.
disk_config: 'AUTO',
flavor: null, // REQUIRED
instance_count: 1,
key_pair: [], // REQUIRED Server Key
name: null, // REQUIRED
networks: [],
profile: {},
security_groups: [], // REQUIRED Server Key. May be empty.
source_type: null, // REQUIRED for JS logic (image | snapshot | volume | volume_snapshot)
source: [],
vol_create: false, // REQUIRED for JS logic
vol_device_name: 'vda', // May be null
vol_delete_on_terminate: false,
vol_size: 1
};
}
/**
* #ngdoc method
* #name launchInstanceModel.initialize
* #returns {promise}
*
* #description
* Send request to get all data to initialize the model.
*/
function initialize(deep) {
var deferred, promise;
// Each time opening launch instance wizard, we need to do this, or
// we can call the whole methods `reset` instead of `initialize`.
initializeNewInstanceSpec();
if (model.initializing) {
promise = initPromise;
} else if (model.initialized && !deep) {
deferred = $q.defer();
promise = deferred.promise;
deferred.resolve();
} else {
model.initializing = true;
model.allowedBootSources.length = 0;
promise = $q.all([
getImages(),
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones, noop),
novaAPI.getFlavors(true, true).then(onGetFlavors, noop),
novaAPI.getKeypairs().then(onGetKeypairs, noop),
novaAPI.getLimits().then(onGetNovaLimits, noop),
securityGroup.query().then(onGetSecurityGroups, noop),
serviceCatalog.ifTypeEnabled('network').then(getNetworks, noop),
serviceCatalog.ifTypeEnabled('volume').then(getVolumes, noop)
]);
promise.then(
function() {
model.initializing = false;
model.initialized = true;
// This provides supplemental data non-critical to launching
// an instance. Therefore we load it only if the critical data
// all loads successfully.
getMetadataDefinitions();
},
function () {
model.initializing = false;
model.initialized = false;
}
);
}
return promise;
}
/**
* #ngdoc method
* #name launchInstanceModel.createInstance
* #returns {promise}
*
* #description
* Send request for creating server.
*/
function createInstance() {
var finalSpec = angular.copy(model.newInstanceSpec);
cleanNullProperties();
setFinalSpecBootsource(finalSpec);
setFinalSpecFlavor(finalSpec);
setFinalSpecNetworks(finalSpec);
setFinalSpecKeyPairs(finalSpec);
setFinalSpecSecurityGroups(finalSpec);
return novaAPI.createServer(finalSpec);
}
function cleanNullProperties(finalSpec){
// Initially clean fields that don't have any value.
for (var key in finalSpec) {
if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) {
delete finalSpec[key];
}
}
}
//
// Local
//
function onGetAvailabilityZones(data) {
model.availabilityZones.length = 0;
push.apply(model.availabilityZones, data.data.items
.filter(function (zone) {
return zone.zoneState && zone.zoneState.available;
})
.map(function (zone) {
return zone.zoneName;
})
);
if(model.availabilityZones.length > 0) {
model.newInstanceSpec.availability_zone = model.availabilityZones[0];
}
}
// Flavors
function onGetFlavors(data) {
model.flavors.length = 0;
push.apply(model.flavors, data.data.items);
}
function setFinalSpecFlavor(finalSpec) {
if ( finalSpec.flavor ) {
finalSpec.flavor_id = finalSpec.flavor.id;
} else {
delete finalSpec.flavor_id;
}
delete finalSpec.flavor;
}
// Keypairs
function onGetKeypairs(data) {
angular.extend(
model.keypairs,
data.data.items.map(function (e) {
e.keypair.id = e.keypair.name;
return e.keypair;
}));
}
function setFinalSpecKeyPairs(finalSpec) {
// Nova only wants the key name. It is a required field, even if None.
if(!finalSpec.key_name && finalSpec.key_pair.length === 1){
finalSpec.key_name = finalSpec.key_pair[0].name;
} else if (!finalSpec.key_name) {
finalSpec.key_name = null;
}
delete finalSpec.key_pair;
}
// Security Groups
function onGetSecurityGroups(data) {
model.securityGroups.length = 0;
push.apply(model.securityGroups, data.data.items);
// set initial default
if (model.newInstanceSpec.security_groups.length === 0 &&
model.securityGroups.length > 0) {
model.securityGroups.forEach(function (securityGroup) {
if (securityGroup.name === 'default') {
model.newInstanceSpec.security_groups.push(securityGroup);
}
});
}
}
function setFinalSpecSecurityGroups(finalSpec) {
// pull out the ids from the security groups objects
var security_group_ids = [];
finalSpec.security_groups.forEach(function(securityGroup){
if(model.neutronEnabled) {
security_group_ids.push(securityGroup.id);
} else {
security_group_ids.push(securityGroup.name);
}
});
finalSpec.security_groups = security_group_ids;
}
// Networks
function getNetworks() {
return neutronAPI.getNetworks().then(onGetNetworks, noop);
}
function onGetNetworks(data) {
model.neutronEnabled = true;
model.networks.length = 0;
push.apply(model.networks, data.data.items);
}
function setFinalSpecNetworks(finalSpec) {
finalSpec.nics = [];
finalSpec.networks.forEach(function (network) {
finalSpec.nics.push(
{
"net-id": network.id,
"v4-fixed-ip": ""
});
});
delete finalSpec.networks;
}
// Boot Source
function getImages(){
return glanceAPI.getImages({status:'active'}).then(onGetImages);
}
function isBootableImageType(image){
// This is a blacklist of images that can not be booted.
// If the image container type is in the blacklist
// The evaluation will result in a 0 or greater index.
return NON_BOOTABLE_IMAGE_TYPES.indexOf(image.container_format) < 0;
}
function onGetImages(data) {
model.images.length = 0;
push.apply(model.images, data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(!image.properties || image.properties.image_type !== 'snapshot');
}));
addAllowedBootSource(model.images, SOURCE_TYPE_IMAGE, gettext('Image'));
model.imageSnapshots.length = 0;
push.apply(model.imageSnapshots,data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(image.properties && image.properties.image_type === 'snapshot');
}));
addAllowedBootSource(model.imageSnapshots, SOURCE_TYPE_SNAPSHOT, gettext('Instance Snapshot'));
}
function getVolumes(){
var volumePromises = [];
// Need to check if Volume service is enabled before getting volumes
model.volumeBootable = true;
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, gettext('Volume'));
addAllowedBootSource(model.volumeSnapshots, SOURCE_TYPE_VOLUME_SNAPSHOT, gettext('Volume Snapshot'));
volumePromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
volumePromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
// Can only boot image to volume if the Nova extension is enabled.
novaExtensions.ifNameEnabled('BlockDeviceMappingV2Boot')
.then(function(){ model.allowCreateVolumeFromImage = true; });
return $q.all(volumePromises);
}
function onGetVolumes(data) {
model.volumes.length = 0;
push.apply(model.volumes, data.data.items);
}
function onGetVolumeSnapshots(data) {
model.volumeSnapshots.length = 0;
push.apply(model.volumeSnapshots, data.data.items);
}
function addAllowedBootSource(rawTypes, type, label) {
if (rawTypes && rawTypes.length > 0) {
model.allowedBootSources.push({
type: type,
label: label
});
}
}
function setFinalSpecBootsource(finalSpec) {
finalSpec.source_id = finalSpec.source && finalSpec.source[0] && finalSpec.source[0].id;
delete finalSpec.source;
switch (finalSpec.source_type.type) {
case SOURCE_TYPE_IMAGE:
setFinalSpecBootImageToVolume(finalSpec);
break;
case SOURCE_TYPE_SNAPSHOT:
break;
case SOURCE_TYPE_VOLUME:
setFinalSpecBootFromVolumeDevice(finalSpec, 'vol');
break;
case SOURCE_TYPE_VOLUME_SNAPSHOT:
setFinalSpecBootFromVolumeDevice(finalSpec, 'snap');
break;
default:
// error condition
console.log("Unknown source type: " + finalSpec.source_type);
}
// The following are all fields gathered into simple fields by
// steps so that the view can simply bind to simple model attributes
// that are then transformed a single time to Nova's expectation
// at launch time.
delete finalSpec.source_type;
delete finalSpec.vol_create;
delete finalSpec.vol_device_name;
delete finalSpec.vol_delete_on_terminate;
delete finalSpec.vol_size;
}
function setFinalSpecBootImageToVolume(finalSpec){
if(finalSpec.vol_create) {
// Specify null to get Autoselection (not empty string)
var device_name = finalSpec.vol_device_name ? finalSpec.vol_device_name : null;
finalSpec.block_device_mapping_v2 = [];
finalSpec.block_device_mapping_v2.push(
{
'device_name': device_name,
'source_type': SOURCE_TYPE_IMAGE,
'destination_type': SOURCE_TYPE_VOLUME,
'delete_on_termination': finalSpec.vol_delete_on_terminate ? 1 : 0,
'uuid': finalSpec.source_id,
'boot_index': '0',
'volume_size': finalSpec.vol_size
}
);
}
}
function setFinalSpecBootFromVolumeDevice(finalSpec, sourceType) {
finalSpec.block_device_mapping = {};
finalSpec.block_device_mapping[finalSpec.vol_device_name] = [
finalSpec.source_id,
':',
sourceType,
'::',
(finalSpec.vol_delete_on_terminate ? 1 : 0)
].join('');
// Source ID must be empty for API
finalSpec.source_id = '';
}
// Nova Limits
function onGetNovaLimits(data) {
angular.extend(model.novaLimits, data.data);
}
// Metadata Definitions
/**
* Metadata definitions provide supplemental information in detail
* rows and should not slow down any of the other load processes.
* All code should be written to treat metadata definitions as
* optional, because they are never guaranteed to exist.
*/
function getMetadataDefinitions() {
// Metadata definitions often apply to multiple
// resource types. It is optimal to make a single
// request for all desired resource types.
var resourceTypes = {
flavor: 'OS::Nova::Flavor',
image: 'OS::Glance::Image',
volume: 'OS::Cinder::Volumes'
};
angular.forEach(resourceTypes, function (resourceType, key) {
glanceAPI.getNamespaces({
'resource_type': resourceType
}, true)
.then(function (data) {
var namespaces = data.data.items;
// This will ensure that the metaDefs model object remains
// unchanged until metadefs are fully loaded. Otherwise,
// partial results are loaded and can result in some odd
// display behavior.
if(namespaces.length) {
model.metadataDefs[key] = namespaces;
}
});
});
}
return model;
}
]);
})();
Status
The array or [] parameter is needed to specify dependent modules when you declare your own module, so this should only happen once per module.
The second notation, without the parameter is just retrieving the module so you can attach controllers/services/filters/... to it.
Use the array notation for the declaration of your module, use the single parameter notation if you want to add something to it.
For example:
in app.module.js
//You want to make use of the ngRoute module,
//so you have to specify a dependency on it
angular.module('app', ['ngRoute']);
You will only specify the dependencies on your module once, when you declare it.
in main.controller.js
//You want to add a controller to your module, so you want to retrieve your module
angular.module('app').controller('mainCtrl', mainCtrl);
function mainCtrl() { };
Now angular will try to find a module by that name instead of creating one, when it doesn't find one, you'll get some errors, which explains your original question.
You will typically do this every time you want to add something to your module.
Note that you could also achieve this by storing your module in a global variable when you create it and then access the module by that variable when you want to add things to it, however as you probably know, creating global variables is a bad practice.
Facing an error with angular is a bliss because it provides the link to description of the error in the console.
From an example page like that...
When defining a module with no module dependencies, the array of dependencies should be defined and empty.
var myApp = angular.module('myApp', []);
To retrieve a reference to the same module for further configuration, call angular.module without the array argument.
var myApp = angular.module('myApp');

Resources