How to make a simulated sync AJAX call in AngularJS? - javascript

I have this script in my app.js:
app.run(['$http', '$location', 'myAppConfig', function ($http, $location, myAppConfig) {
if (myAppConfig.webAPIPath.main == '') {
var getconfigDone = false;
$http.get('fileHandler.ashx?action=getconfig')
.then(function (result) {
if (JSON.parse(result.data.Data).APIURL !== undefined && JSON.parse(result.data.Data).APIURL != '') {
var apiURL = JSON.parse(result.data.Data).APIURL;
if (apiURL.lastIndexOf('/') + 1 == apiURL.length) {
apiURL = apiURL.substring(0, apiURL.lastIndexOf('/'))
}
myAppConfig.webAPIPath.main = apiURL + "/";
myAppConfig.webAPIPath.account = myAppConfig.webAPIPath.main + '/api/OnlineApplicationPortal/v1/Account/';
myAppConfig.webAPIPath.dashboard = myAppConfig.webAPIPath.main + '/OnlineApplicationPortal/v1/Dashboard/';
}
else {
$location.path('Action/Welcome/apiUrlError');
}
//debugger
getconfigDone = true;
}, function (response) { debugger }
);
}
}]);
Also I have got this factory object which uses the myAppConfig in app.js:
(function () {
angular
.module('app.data')
.factory('accountDS', ['$http', '$routeParams', 'myAppConfig', function ($http, $routeParams, myAppConfig) {
var pathPrefix = myAppConfig.webAPIPath.account;
var createAccount = function (account, email) {
var OnlineApplicationPortalModel = {
Name: account.firstName,
Surname: account.lastName,
Email: email,
Password: account.password
};
return $http.post(pathPrefix + 'CreateAccount', OnlineApplicationPortalModel)
.then(function (response) {
return response;
});
};
var confirmEmail = function () {
var data = {
guid: $routeParams.guid
};
return $http.post(pathPrefix + 'ConfirmEmail', data)
.then(function (response) {
return response;
});
}
return {
createAccount: createAccount,
confirmEmail: confirmEmail
};
}]);
})();
The service object needs to use myAppConfig.webAPIPath.account which is resolved in the function in app.js run function. Now the problem is sometimes the browser reaches the service code sooner than than the AJAX call is returned, a race condition. I know that it is not possible in AngularJS to make a sync AJAX call. So how can I solve this?

If I correctly understand you, you want to myAppConfig.webAPIPath.account resolve this value to use it later in your code, but ajax call which provides you value for this variable is not always called before assignment. I think you could use https://docs.angularjs.org/api/ng/service/$q to solve your problem. Your code in myAppConfig should be inside function, so you can call it inside your factory and return deferred object, which then when your .account variable is set should call code from accountDS factory.

Related

How to use angular 1.6.5 call $http request recursively when data set returned is limited

I would like to use Angular 1.6.5 for a project rebuild, but I'm not sure how to use the $http.get request in a factory when the source returns only a limited number of records at a time (1000 returned per request) and there are over 2000 records that I need to get.
In my current code I use jquery ajax and in the .done method I check for the presence of the value "__next", and if it exists, I recall the function passing the value "__next". When the "__next" value isn't returned, I do something with the data.
function getSpecifiedList(url){
var specUrl = url;
$.ajax({
url: specUrl,
type: "GET",
headers:{"accept":"application/json;odata=verbose",
error: function(xhr){
console.log(xhr.status + " " + xhr.statusText);
}
}
}).done(function (results){
$("#wc_report_holder").text(results.length);
//buildObjects processes the results and adds to an array
buildObject(results);
if(results.d.__next){
getSpecifiedList(results.d.__next);
}else{
buildGridView();
}
}).fail(function(error){
$("#wc_report_holder").text("There was an error: " + error);
});
}
I would like to figure out how to implement that same value check and recursive call in angular 1.6.5 using best practice and most efficient but I haven't had luck figuring it out based on the angular docs and Googling.
Here is a short version of what I currently have using Angular 1.6.5.
<script>
var sitesApp = angular.module("sitesApp", ['ngRoute']);
sitesApp.controller('SitesListCtrl', ['$scope', 'sites',
function ($scope, sites) {
sites.list().then(function (response) {
$scope.sites = response.data.value;
});
}
]);
sitesApp.controller("SiteDetailsCtrl", ['$scope', '$routeParams', 'sites',
function ($scope, $routeParams, sites) {
sites.find($routeParams.SiteCodePc, function (site) {
$scope.site = site;
});
}
]);
sitesApp.config(function ($routeProvider, $locationProvider) {
$locationProvider.hashPrefix('!');
$routeProvider.
when('/', {
templateUrl: 'https://machine/sites/site-list.html',
controller: 'SitesListCtrl'
}).
when('/:SiteCodePc', {
templateUrl: 'https://machine/sites/site-details.html',
controller: 'SiteDetailsCtrl'
}).
otherwise({
redirectTo: '/'
});
});
sitesApp.factory('sites', ['$http', function ($http) {
var urlBase = "https://some-endpoint-for-data";
var cachedData;
function getData(callback) {
if (cachedData) {
callback(cachedData);
} else {
return $http({
method: 'GET',
url: urlBase
})
.then(function (response) {
//HERE IS WHERE I THINK THE SOLUTION NEEDS TO BE IMPLEMENTED
cachedData = response;
return cachedData;
});
}
}
return {
list: getData,
find: function (SiteCodePc, callback) {
getData(function (response) {
var site = response.data.value.filter(function (entry) {
//debugger;
return entry.SiteCodePc === SiteCodePc;
});
callback(site[0]);
});
}
};
}]);
</script>
<div ng-app="sitesApp">
<div ng-view></div>
</div>
Thanks in advance
It looks like you can do a simple recursion where you accept a second (optional) parameter. If you are calling getData() for the first time then you can get your first 1000 results. However if you find __next then you will call it again sending the current 1000 results you have and concat the next 1000 results with the previous 1000.
sitesApp.factory('sites', ['$http', function ($http) {
var urlBase = "https://some-endpoint-for-data";
function getData(callback, results) {
return $http({
method: 'GET',
url: urlBase
})
.then(function (response) {
// If you have found a previous batch of results then concat the two arrays
if(results) {
response = response.concat(results);
}
// If there are more results to be found then recursively call the same function passing the batched results
if(response.__next) {
return getData(callback, response);
}
// If there are no more results to be found then trigger your callback function
else {
callback(response);
}
});
}
return {
list: getData,
find: function (SiteCodePc, callback) {
getData(function (response) {
var site = response.data.value.filter(function (entry) {
//debugger;
return entry.SiteCodePc === SiteCodePc;
});
callback(site[0]);
});
}
};
}]);
I have implemented same kind of scenario with pagination logic and $q. In this sample code I am pulling the records recursively as lazy based on the LazyloadingLimit. You can specify the limit based on your requirement.So it only pulls the records based on the count from the total collection. In this below sample I am not using $http. On your real sample you can use the $http to pull the records from the server. Here I just hard coded the collection initially.
In your case you have to fetch total records count initially and apply some pagination logic or some other parameter to pull the next records.
angular.module('app', []);
angular.module('app').controller('SampleController', function ($scope,$http, $timeout, $q) {
// $scope.initialize();
$scope.mainCount = 0;
$scope.lazyloadingLimit = 2;
$scope.tileDefinitions = null;
$scope.tempList = null;
$scope.totalRecordCollection = [
{ "Name": "Record1" },
{ "Name": "Record2" },
{ "Name": "Record3" },
{ "Name": "Record4" },
{ "Name": "Record5" },
{ "Name": "Record6" },
{ "Name": "Record7" },
];
function getTotalRecordCollection() {
var deferred = $q.defer();
deferred.resolve($scope.totalRecordCollection);
return deferred.promise;
}
$scope.initialize = function () {
debugger;
var currentCount=0;
var pageList = new Array();
var currentPage = 1;
var numberPerPage = 2;
var numberOfPages = 0;
function makeList() {
numberOfPages = getNumberOfPages();
}
function getNumberOfPages() {
return Math.ceil($scope.tempList.length / numberPerPage);
}
function nextPage() {
currentPage += 1;
}
function loadList() {
var deferred = $q.defer();
if (currentCount !== $scope.tempList.length) {
var begin = ((currentPage - 1) * numberPerPage);
var end = begin + numberPerPage;
pageList = $scope.tempList.slice(begin, end);
currentCount = currentCount + pageList.length;
$scope.mainCount = currentCount;
deferred.resolve(true);
} else {
debugger;
return $q.reject();
}
return deferred.promise;
}
function loadNextRecords() {
loadList().then(function (res) {
nextPage();
loadNextRecords();
});
}
getTotalRecordCollection().then(function (response) {
debugger;
$scope.tempList = response;
makeList();
loadNextRecords();
});
}
});
<body ng-controller="SampleController">
<input type="button" value="Click Here" ng-click="initialize()"/>
{{mainCount}}
</body>
Once all the records are loaded , you should reject the promise else the recursive loops never end.
Hope this helps

How to pass param from controller to service in AngularJs

I'm currently working on a project to help me better understand angularjs! I am currently stuck on how to pass a parameter from the controller to service.
In my program, I have created a function called "GetForecastByLocation" when a user types in an input clicks on a button. From there I want to take their input and then pass it to the http call in service.js.
Originally, $http.get was in a long giant string of the API url, but I googled around and it seems that I'm supposed to use parameters when trying to change a portion of the string. As of right now, I know parameter is hardcoded to a specific city, but I want to take new input and pass the value of vm.city to the $http.get call.
If any one can help I would greatly appreciate it. Thank you!
controller.js
var app = angular.module('weatherApp.controllers', [])
app.controller('weatherCtrl', ['$scope','Data',
function($scope, Data) {
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);},
Data.getApps(city)
.then(function(data)){
//doing a bunch of things like converting units, etc
},
function(res){
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
}); // end of function
}]) // end of controller
service.js
.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
promise = $http.get('http://api.openweathermap.org/data/2.5/weather?',{
params: {
q: Tokyo,
}
})
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
Passing arguments to a factory method is no different than passing arguments to a plain old function.
First, set up getApps to accept a parameter:
.factory('Data', function($http, $q){
// ...
return {
getApps: function(city){
promise = $http.get(URL, {
params: {q: city}
}).then( /* ... */ );
// ...
return promise;
}
};
});
Then pass it your argument:
$scope.getForecastByLocation = function(myName) {
$scope.city = myName;
Data.getApps($scope.city);
}
It's just like setting a value to a function's context variable.
Services.js
Simple example of a service.
.factory('RouteService', function() {
var route = {}; // $Object
var setRoute_ = function(obj)
{
return route = obj;
};
var getRoute_ = function()
{
if(typeof route == 'string')
{
return JSON.parse(route);
}
return null;
};
return {
setRoute: setRoute_,
getRoute: getRoute_
};
})
Controllers.js
Simple example of Service usage:
.controller('RoutesCtrl', function ($scope, RouteService) {
// This is only the set part.
var route = {
'some_key': 'some_value'
};
RouteService.setRoute(route);
})

Angular service issue -- returning an array in a service containing promises

As you can see this is my first time attempting this and I appear to be doing it incorrectly. I just want to take some code, consisting of promises and http requests, and put it in a service before the controller uses it. My goal is to simply clean up the controller so it doesn't contain all of that code.
After logging it in the last step of the controller the object appears as undefined. Also, all the requests are being made successfully. So, it's jumping through all the hoops fine so I'm guessing it must not be returning any value in the service and nothing gets passed on to the subsequent function in the controller. How can I return the 'people' array in the service after the promises have been fulfilled?
var myApp = angular.module('myApp', []);
myApp.controller('AppCtrl', function ($scope, $http, dataService) {
var getPeople = function () {
return $http.get('/getpeople');
};
getPeople().then(function (response) {
dataService.compilePeople(response)
})
.then(function (people) {
console.log(people);
$scope.people = people;
});
});
myApp.service('dataService', function ($q, $http) {
this.compilePeople = function (response) {
var people = [];
names = response.data;
grandPromiseArray = [];
names.forEach(function (index) {
var name = index,
count = $q.defer(),
skills = [],
urls = '/getskillsbyname/' + name,
urlc = '/getcountbyname/' + name;
grandPromiseArray.push(
$q.all([$http.get(urls), $http.get(urlc)])
.then(function (response) {
people.push({
name: name,
skills: response[0].data,
count: response[1].data
});
})
);
});
return $q.all(grandPromiseArray).then(function () {
return people
});
}
});
You need to return the promise from compilePeople() in order for the people to be passed into the next .then() handler. so close ;)
getPeople()
.then(function (response) {
//You were missing this return
return dataService.compilePeople(response)
})
.then(function (people) {
console.log(people);
$scope.people = people;
});

how to retrieve data by id with angularfire

i used a tutorial to create a angularfire chat app. it is a standalone app that uses ui-router. I integrated it succssfully as a view in my app but that is not practical. I need to be able to use the chat on any view I am at. I am stuck at moving a resolve function to a controller. I have read some docs and I believe it is returning a promise that I need to resolve in the controller. the link to the tutorial is here.
tutorial
here is the ui-router I am trying to get away from
.state('channels.direct', {
url: '/{uid}/messages/direct',
templateUrl: 'views/chat/_message.html',
controller: 'MessageController',
controllerAs: 'messageCtrl',
resolve: {
messages: function ($stateParams, MessageService, profile) {
return MessageService.forUsers($stateParams.uid, profile.$id).$loaded();
},
channelName: function ($stateParams, UserService) {
return UserService.all.$loaded().then(function () {
return '#' + UserService.getDisplayName($stateParams.uid);
});
}
}
})
The message service
var channelMessagesRef = new Firebase(AppConstant.FirebaseUrl + 'channelMessages');
var userMessagesRef = new Firebase(AppConstant.FirebaseUrl + 'userMessages')
return {
forChannel: function (channelId) {
return $firebaseArray(channelMessagesRef.child(channelId));
},
forUsers: function (uid1, uid2) {
var path = uid1 < uid2 ? uid1 + '/' + uid2 : uid2 + '/' + uid1;
return $firebaseArray(userMessagesRef.child(path));
}
};
the user service
var usersRef = new Firebase(AppConstant.FirebaseUrl + 'users');
var connectedRef = new Firebase(AppConstant.FirebaseUrl + '.info/connected');
var users = $firebaseArray(usersRef);
return {
setOnline: function (uid) {
var connected = $firebaseObject(connectedRef);
var online = $firebaseArray(usersRef.child(uid + '/online'));
connected.$watch(function () {
if (connected.$value === true) {
online.$add(true).then(function (connectedRef) {
connectedRef.onDisconnect().remove();
});
}
});
},
getGravatar: function (uid) {
return '//www.gravatar.com/avatar/' + users.$getRecord(uid).emailHash;
},
getProfile: function (uid) {
return $firebaseObject(usersRef.child(uid));
},
getDisplayName: function (uid) {
return users.$getRecord(uid).displayName;
},
all: users
};
here is what I have so far in the controller
$scope.directMessage = function (uid) {
UserService.all.$loaded().then(function () {
$scope.selectedChatUser = '#' + UserService.getDisplayName(uid);
});
$scope.selectedChatUserMessages = MessageService.forUsers(uid, profile.$id).$loaded();
};
I am returning the
$scope.selectedChatUser
fine. the issue is with the Message Service
this is the what i am currently returning from the message service
$$state: Object
__proto__: Promise
how do i resolve this?
You're trying to return from inside a promise in your channelName function.
The object you're getting back is an unresolved promise. You want the resolved data from the promise injected into your controller.
You need to create a to return from this function.
.state('channels.direct', {
url: '/{uid}/messages/direct',
templateUrl: 'views/chat/_message.html',
controller: 'MessageController',
controllerAs: 'messageCtrl',
resolve: {
messages: function ($stateParams, MessageService, profile) {
return MessageService.forUsers($stateParams.uid, profile.$id).$loaded();
},
channelName: function ($stateParams, UserService, $q) {
// create a deferred for this function
var deferred = $q.defer();
// load async data with UserService.all's promise
UserService.all.$loaded()
.then(function () {
var name = UserService.getDisplayName($stateParams.uid);
deferred.resolve(name);
});
// return promise
return deferred.promise;
}
}
})
However in getDisplayName I would just recommend returning back the object rather than just the name, as the entire set is synchronized by Firebase.

Angular $resource

I am using the following pattern for my REST API, but vm.listing in my controller is always undefined? Probably my pattern is not right? Is there a different pattern to use here? I don't want to call the .get(..) in my controller code.
.factory("listingsResource", ["$resource", "$q", 'appSettings',
function ($resource, $q, appSettings) {
return $resource(appSettings.serverPath + "api/Listings/:id")
}]);
.factory("editService",
var _listing;
var _getListing = function (listingId) {
_listing = listingsResource.get({
id: listingId
});
}
return {
listing: _listing,
getListing: _getListing
};
Controller Code:
createService.getListing(listingId);
vm.listing = createService.listing;
The problem is that when you call listingsResource.get() it returns a promise. Not the data response.
You have to pass the get() a success callback and then set the listing variable inside this callback. I would do something like this:
.service("listingsService", ["$resource", "$q", 'appSettings',
function ($resource, $q, appSettings) {
var _this = this;
_this.listing = {};
var listingResource = $resource(appSettings.serverPath + "api/Listings/:id");
this.getListing = function(listingId){
listingResource.get({id: listingId},
function (data) {
// Success callback
// Set listing keys-value pairs
// do not do: _this.listing = data
_this.listing.id = data.id;
_this.listing.title = data.title;
},
function(err) {
// error callback
console.log(err);
}
)
}
}
])
This works fine with a factory aswell if you prefer. Then in the controller:
listingService.getListing(listingId);
vm.listing = listingService.listing;

Categories