I have an asynchronous web service which will return status "pending" immediately until it returns either 200 or an error.
This is my code so far:
$http.get('/myweb/services/callService').success(function(response, status, headers, config){
...handling success
}).error(function(err, status, headers, config){
//handling failure
});
//called just after $http.get
$scope.askProgress();
Where askProgress is:
$http.get('/myweb/services/progress').success(function(response, status, headers, config){
console.log(response);
$scope.reportProgress = response.description;
if(response.description < 100){//description is a 0-100 value indicating progress
...updating status...
$timeout(function() {$scope.askProgress();}, 1000); //calling again
}else{
$scope.reportProgressL = "Done!";
}
}).error(function(err, status, headers, config){
alert('Error: '+err+" "+status);
});
My problem is that the first call to askProgress is made before the service returns status "pending" leading to a non consistent progress value.
I'd like for the askProgress function to be called just after the service gives me the first "pending"...is it possible?
Not sure I understand you problem fully but it sounds like you need to be calling your askProgress() function after the first http request returns. If so, have you tried putting the call inside your then() call?
$http
.get('/myweb/services/callService')
.then(function(response, status, headers, config){
return $scope.askProgress();
})
.then(function(progressResponse){
})
.catch(function(error){});
Update
I think you will need to register an $http interceptor to track the state of your request.
Try this:
DEMO
app.js
var app = angular.module('plunker', []);
app.factory('myHttpInterceptor', [ function() {
var pendingRequests = {};
return {
getPendingRequests: function(){
return pendingRequests;
},
request: function(request) {
console.log('*** request made # %s ***', new Date());
pendingRequests[request.url] = true;
console.log('pendingRequests', pendingRequests);
console.log('**************************');
return request;
},
response: function(response) {
console.log('*** response received # %s ***', new Date());
var url = response.config.url;
if(pendingRequests.hasOwnProperty(url)){
delete pendingRequests[url];
}
console.log('pendingRequests', pendingRequests);
console.log('**************************');
return response;
}
};
}]);
app.config(['$httpProvider', function($httpProvider) {
// register our factory as an http interceptor
// in the config phase
$httpProvider.interceptors.push('myHttpInterceptor');
}]);
app.controller('MainCtrl', function($scope, $http, $timeout, myHttpInterceptor) {
var targetUrl = 'big-data.json';
$scope.callService = function(){
console.log('*** call service ***');
return $http.get(targetUrl)
.then(function(){
console.log('********** done, success **********');
})
.catch(function(){
console.log('********** done, error **********');
});
}
$scope.askProgress = function(){
var pendingReqs = myHttpInterceptor.getPendingRequests();
// the request in this demo is very quick
// so I have had to change the time between checks
// you will probably want to change this for your
// own app
return $timeout(1)
.then(function(){
if(pendingReqs.hasOwnProperty(targetUrl)){
console.log('*** stil pending ***');
return $scope.askProgress();
}
console.log('*** no pending requests ***');
$timeout.cancel();
})
}
$scope.callService();
$scope.askProgress();
});
Related
I know I amy look like foolish while asking this, but I am not able to figure this out.
I have written a service which handles the post call to the server. $q service is returning the promise back to the controller function which has called the service.
Service :
app.service('AjaxService', ['$http','$q','$log', function($http,$q,$log) {
return {
getSearchresultPost : function(url,data){
var defer = $q.defer();
$http.post(url, data)
.then(function(data, status, header, config){
defer.resolve(data);
}).then(function(data, status, header, config){
defer.reject(data);
});
return defer.promise;
}
};
}]);
Controller
app.controller("kitGuideNavigationController",['$scope','$window','$timeout','AjaxService',function($scope,$window,$timeout,AjaxService){
AjaxService.getSearchresultPost("/services/knowledge/getProducts",pathToCall)
.then(function(data){
console.log("Data ",data);
}).then(function(data){
console.log("Some Error Occured");
});
}]);
When I try to run the code I get both the consoles getting printed.
I am not getting what is getting wrong.Can someone help?
change the second "then" to "catch", should fix this. You have to do this in both cases.
app.controller("kitGuideNavigationController",['$scope','$window','$timeout','AjaxService',function($scope,$window,$timeout,AjaxService){
AjaxService.getSearchresultPost("/services/knowledge/getProducts",pathToCall)
.then(function(data){
console.log("Data ",data);
}).catch(function(data){
console.log("Some Error Occured");
});
}]);
update
also as I saw, you are using the $http, check here
You can change your service and pass a second parameter(error function) in $http.post like this(documentation: https://docs.angularjs.org/api/ng/service/$http):
app.service('AjaxService', ['$http','$q','$log', function($http,$q,$log) {
return {
getSearchresultPost : function(url,data){
var defer = $q.defer();
$http.post(url, data)
.then(function(data, status, header, config){
defer.resolve(data);
}, function(error, status, header, config){
defer.reject(error);
});
return defer.promise;
}
};
}]);
And in your controller you can pass a second parameter too:
app.controller("kitGuideNavigationController",['$scope','$window','$timeout','AjaxService',function($scope,$window,$timeout,AjaxService){
AjaxService.getSearchresultPost("/services/knowledge/getProducts",pathToCall)
.then(function(data){
console.log("Data ",data);
}, function(error){
console.log("Some Error Occured", error);
});
}]);
There is no need to manufacture a promise with $q.defer as the $http service already returns a promise:
app.service('AjaxService', ['$http','$q','$log', function($http,$q,$log) {
return {
getSearchresultPost : function(url,data){
//var defer = $q.defer();
var promise = $http.post(url, data)
//.then(function(data, status, header, config){
.then(function (response) {
var data = response.data;
//defer.resolve(data);
//return data to resolve
return data;
//}).then(function(data, status, header, config){
}).catch(function(response) {
var data = response.data;
//defer.reject(data);
//throw data to reject with value
throw data;
});
//return defer.promise;
return promise;
}
};
}]);
Also notice that the .then and .catch methods invoke their functions with a response object, not data but returning (or throwing) the data property to the handler creates a new promise that resolves (or rejects) with that value.
AjaxService.getSearchresultPost(url,pathToCall)
.then(function(data){
console.log("Data ",data);
//}).then(function(data){
}).catch(function(data) {
console.log("Some Error Occured");
});
For more information, see AngularJS $q Service API Reference - Chaining Promises
I need to finish all my ajax calls to enable a button, but I am not getting all my promises done before enabling the button.
I have this piece of code with all my ajax gets:
$q.all([
$scope.load_ocupacoes(),
$scope.load_tipos_pisos(),
$scope.load_tipos(),
$scope.load_caracteristicas(),
$scope.load_amenidades(),
$scope.load_subtipos(true, 'incluir')
]).then(function() {
console.log('loading complete !!!');
$scope.theglyphicon = 'fa fa-check fa-fw';
$scope.isDisabledButton = false;
});
Each load function is a $http.get, like that:
$scope.load_ocupacoes = function() {
$http.get(url_api_status_ocupacao)
.success(function(response) {
console.log(response);
$scope.status_ocupacoes = response;
})
.error(function(response) {
console.log(response);
ngToast.create({
className: 'danger',
content: 'Não foi possível recuperar a lista.'
});
});
};
I have also tried this way:
$scope.load_ocupacoes = function()
{$resource(url_api_status_ocupacao).query().$promise.then(function(response) {
console.log(response);
$scope.status_ocupacoes = response;
});
};
And this... but with the same problem:
$scope.load_ocupacoes = function() {
$timeout(function(){
$scope.$apply(function() {
$scope.status_ocupacoes = appFactory.recuperarLista(url_api_status_ocupacao)
.then(function(result) {
console.log(result);
$scope.status_ocupacoes = result;
});
});
});
};
But, I am getting the message 'loading complete !!!' before the end of all loading.
Is there any problem with this approach?
There could be more errors, but the basic misunderstanding is that $q.all takes promises, and all your functions return undefined (because they don't have a return statement) - so instead of getting six promises, your $q.all gets six undefineds. AFAIK, $http.get returns a promise by default, so one way to fix it would be to just add return statement to each of your functions, in front of $http.get, like this:
$scope.load_ocupacoes = function() {
return $http.get(url_api_status_ocupacao)
.then(function(response) {
});
};
I guess $q.all accept promises.
This must be apply to all other's related method.
$scope.load_ocupacoes = function() {
$http.get(url_api_status_ocupacao)
// use then instead success
.then(function(response) {
// return raw promise instead actual value
return response;
}, console.log('error));
};
$q.all requires an array of promises but your are providing a function which is neither returning any promise.
You can do this :
$q.all([
$http.get(url_api_status_ocupacao),
$http.get(url_api1),
$http.get(url_api2)
]).then(function() {
......
});
I have resolved my problem with this approach:
var promises = [appFactory.recuperarLista(url_api_status_ocupacao),
appFactory.recuperarLista(url_api_tipos_pisos),
appFactory.recuperarLista(url_api_caracteristicas),
appFactory.recuperarLista(url_api_amenidades)
];
$q.all(promises).then(function (responses) {
$scope.status_ocupacoes = responses[0];
$scope.tipos_pisos = responses[1];
$scope.caracteristicas = responses[2];
$scope.amenidades = responses[3];
}).then(function() {
console.log('All Loading completed !!!');
});
And I made a factory returning promises:
angular.module('starter.services', ['datatables'])
.factory('appFactory', function($http, $q) {
return {
recuperarLista: function(url) {
var deferred = $q.defer();
$http({ method: "GET", url: url })
.success(function (data, status, headers, config) {
deferred.resolve(data);
}).error(function (data, status, headers, config) {
deferred.reject(status);
});
console.log('loading for ' + url + ' was completed !!!');
return deferred.promise;
}
};
});
Now I am getting this console output:
loading for api/loadliststatusocupacoes was completed !!!
services.js:171
loading for api/loadlisttipospisos was completed !!!
services.js:171
loading for api/loadlistcaracteristicas was completed !!!
services.js:171
loading for api/loadlistamenidades was completed !!!
services.js:171
All Loading completed !!!
imovel-controller.js:690
I have set the username value in rootscope and when I try to use it within the same function it becomes undefined. I am not sure why this is happening.
controller
xxxApp.controller('listController', function($scope, $http, $location, $routeParams, $log, uiGridConstants, $rootScope) {
$scope.isSelected = false;
$scope.showSpinner = true;
$rootScope.loggedInUser = {};
$scope.user = {};
$http.get("/mdm/getLoggedInUsername")
.success(function(data, status, headers, config) {
$scope.user = data.user;
$rootScope.loggedInUser = $scope.user;
console.log("the logged in user is 1" +$scope.user);
console.log("the rootscope logged in user is 1" +$rootScope.loggedInUser);
})
.error(function(data, status, headers, config, statusText) {
console.log("Error ....the logged in user is 1" +$scope.user);
});
console.log("the rootscope logged in user is 2" +$scope.user);
$http.get("/mdmservice/services/entities/" +$rootScope.loggedInUser) // here rootscope is undefined and throws me 404 error not found
.success(
function(data, status, headers, config) {
$scope.entities = data;
})
.error(function(data, status, headers, config, statusText) {
$scope.error = true;
$scope.errorMessage = "A system error occured."
})
.finally(function () {
$scope.showSpinner = false;
});
That happens because $http calls are asynchronous - that means your program executes in the following order:
start first http call
print console log statement
start second http call
execute the success or error callbacks once the respective http call is finished
So to make your script work you can put the console.log statement and your second http call within the success callback of the first http call.
If you have trouble understanding this, I would recommend reading up on asynchronous programming in javascript with Callbacks and Promises.
That's because the first get function hasn't finished yet. You should call the second in callback of the first (in success area).
In my app, I have an event that listens for new messages sent to a user. Upon receiving the event, it runs a factory function to retrieve messages. However, it seems as though it is always 1 event behind (ie, event 1 data doesn't display until event 2 occurs).
I have a feeling this has to do with the digest cycle. I have tried $scope.$apply, $timeout to no avail. Hopefully I have been clear enough.
$scope.retrieveMessages = function(){
Conversations.retrieveConversations($scope.authentication.uid)
.then(function(success){
$scope.messageList = success;
}, function(error){
console.log(error);
});
};
$scope.$on('$RECEIVED_MESSAGE', function (event, data) {
$scope.retrieveMessages();
$scope.$apply();
});
Service
angular
.module('conversations')
.factory('EventEmitter', ['$rootScope',
function($rootScope) {
var factory = {
newMessage: function() {
$rootScope.$broadcast('$RECEIVED_MESSAGE');
}
};
return factory;
}]);
Function in controller that watches firebase for changes
var notificationsRef = new Firebase(config.firebaseRef + 'notifications/' + $scope.authentication.uid);
notificationsRef.limitToLast(1).on('child_added', function(childSnapshot, prevChildKey) {
var snapshot = childSnapshot.val();
if(snapshot.type === 'Conversation'){
EventEmitter.newMessage();
};
})
.catch(function(error) {
console.error("Error:", error);
});
Conversations Factory (omitted definition and other methods for brevity)
retrieveConversations: function(uid){
var deferred = $q.defer();
var request = {
uid: uid
};
$http.post(config.serverRef + '/conversations', request)
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(status);
});
return deferred.promise;
},
Issue was not with the code but timing and execution. Calls were happening faster than the re-indexing of firebase data to elasticsearch. Solved with $timeout(function(){$scope.retrieveMessages()}, 1000).
I can't seem to get the $httpProvider.interceptors to actually intercept. I created a sample on JSFiddle that logs when the interceptor is run and when the $http response is successful. The request interceptor is run after the response is already returned as successful. This seems a bit backwards.
I can't use transformRequest because I need to alter the params in the config. That part isn't shown in the sample.
I'm using AngularJS 1.1.5
http://jsfiddle.net/skeemer/K7DCN/1/
Javascript
var myApp = angular.module('myApp', []);
myApp.factory('httpInterceptor', function ($q) {
return {
request: function (config) {
logIt('- Modify request');
return config || $q.when(config);
}
};
});
myApp.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
function MyCtrl($scope, $http) {
// Hit a server that allows cross domain XHR
$http.get('http://server.cors-api.appspot.com/server?id=8057313&enable=true&status=200&credentials=false')
.success(function (data) {
//logIt(data[0].request.url);
logIt('- GET Successful');
});
$scope.name = 'Superhero';
}
// Just the logging
var logs = document.getElementById('logs');
function logIt(msg) {
var e = document.createElement('div');
e.innerHTML = msg;
logs.insertBefore(e, logs.firstChild);
}
HTML
<div ng-controller="MyCtrl">Hello, {{name}}!</div>
<br/>
<div id="logs"></div>
If you want the option to accept/reject a request at interception time you should be using $httpProvider.responseInterceptors, see example below:
$httpProvider.responseInterceptors.push(function($q) {
return function(promise){
var deferred = $q.defer();
promise.then(
function(response){ deferred.reject("I suddenly decided I didn't like that response"); },
function(error){ deferred.reject(error); }
);
return deferred.promise;
};
});
EDIT Didn't read your comment, indeed responseInterceptors is now obsolete an that's how you do it instead:
$httpProvider.interceptors.push(function($q) {
return {
request: function(config){ return config; },
response: function(response) { return $q.reject(response); }
};
});
I learned something useful, thanks
The request interceptor isn't running after the data is returned. It's running before. Your logIt function inserts the newest message at the top. If you change your code to use the $log service, you'll see that the interceptor runs first.