I have this array I am getting through the following method:
var url= *url defined here*;
$scope.ViewProfile = function () {
$http.get(url)
.success(function (response) {
$scope.ProfileList = response;
$scope.FavNumbers = $scope.ProfileList[0].FavNumbers;
})
.error(function () {
});
}
I am required to edit the Fav Numbers list on the UI. and post it back to another url through http post url method. What I am stuck is with the concept of asynchronous calls, due to which I am unable to retrieve the favorite numbers list to be available for editing. Please help!
I have tried a method of using promises as follows:
app.factory('myService', function($http) {
var myService = {
async: function(url) {
var promise = $http.get(url).then(function (response) {
console.log(response);
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
In my controller I am doing:
angular.module('JuryApp').controller('mycontroller', ['myService', function (myService) {
myService.async(url).then(function(d) {
$scope.data = d;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
But I keep getting the error 'd is not defined'. It keeps giving an error of some sort, where the debugger goes into an infinite loop or something.
You are overcomplicating it, I think. Async calls are actually pretty simple:
You're service:
app.factory("myService", ["$http", function($http) {
var MyService = {
getData: function(url) {
return $http.get(url); //$http returns a promise by default
}
};
return MyService;
})];
Your controller:
angular.module('JuryApp').controller('mycontroller', ['myService', function (myService) {
$scope.FavNumbers = [];
var url = "http://my.api.com/";
myService.getData(url).then(function(response) {
$scope.FavNumbers = response.data[0].FavNumbers;
});
}]);
That's all that you need to do.
I have the following method getData(url) in a my factory which uses $http.get(url) to get data from an URL
angular
.module('az-app')
.factory('WebServiceFactory', function ($http, $q) {
var WebServiceFactory = this;
WebServiceFactory.getData = function (url) {
var deferred = $q.defer();
$http.get(url)
.then(
function (response) {
deferred.resolve({
data: response
});
}, function (rejected) {
deferred.reject({
data: rejected
});
}
);
//Promise to be returned
return deferred.promise;
}
It works fine but I need to abort the http.get and/or reject the promise so I can display an error message from my controller which has this method:
var getSpecialties = function (type) {
doctorsCtrl.showLoading();
var url = "example.com";
WebServiceFactory.getData(url)
.then(
function (result) {
doctorsCtrl.hideLoading();
var specialtiesArray = result.data.data;
StorageFactory.specialties = specialtiesArray;
doctorsCtrl.specialties = StorageFactory.specialties
//I WANT TO TRIGGER THIS REJECTED FUNCTION when timeout time is finished
}, function (rejected) {
doctorsCtrl.hideLoading();
doctorsCtrl.showAlert();
}
);
}
The service $http accepts, in the config object, a timeout property that answers to what you need. Have a look at the documentation, especially the part about the config object:
timeout – {number|Promise} – timeout in milliseconds, or promise that should abort the request when resolved.
Also, notice that you're using promises in an inefficient way. The following is a promise antipattern:
WebServiceFactory.getData = function (url) {
var deferred = $q.defer();
$http.get(url)
.then(
function (response) {
deferred.resolve(...);
}, function (rejected) {
deferred.reject(...);
}
);
//Promise to be returned
return deferred.promise;
}
You could have simply:
WebServiceFactory.getData = function (url) {
return $http.get(url);
}
With a timeout of 3 seconds it would be:
Service:
WebServiceFactory.getData = function (url) {
return $http.get(url, {timeout: 3000}); // <-- timeout applies ONLY for this call
}
Controller:
WebServiceFactory.getData(url).then(
function (result) {
doctorsCtrl.hideLoading();
doctorsCtrl.specialties = StorageFactory.specialties = result.data;
}, function (rejected) {
doctorsCtrl.hideLoading();
doctorsCtrl.showAlert();
}
);
Notice also that you're calling hideLoading both in case of success and error. You can call it once, in a chained finally handler:
// ...
.finally(function () {
doctorsCtrl.hideLoading();
}
I have this factory that downloads some JSON data and adds it to the $scope.
myApp.factory('loadDataService', function ($rootScope, $http) {
var loadDataService = {};
loadDataService.data = {};
loadDataService.getData = function () {
$http.get('/static/data.json')
.success(function (data) {
console.log("download finish");
loadDataService.data = data;
});
return loadDataService.data;
};
return loadDataService;
});
I call the download service from my main controller like this:
$scope.data = loadDataService.getData();
// if I access the $scope.data here I get and exception because
// the data is not yet downloaded.
Once the data is download and added to the scope I need to do a bunch of operations. What is the proper way of doing a bunch of operations in your controller after your data is downloaded.
Give a callback function in getData like this:
your Factory
loadDataService.getData = function (callback) {
$http.get('/static/data.json')
.success(function (data) {
console.log("download finish");
loadDataService.data = data;
callback();
});
return loadDataService.data;
};
your Controller
$scope.someOperations = function() {
// Your operations
};
$scope.data = loadDataService.getData($scope.someOperations);
You can also use loaded event with $rootScope like this:
$rootScope.$on('data:loaded', function(e, data) {
deferred.resolve(data);
});
Given a Ajax request in AngularJS
$http.get("/backend/").success(callback);
what is the most effective way to cancel that request if another request is launched (same backend, different parameters for instance).
This feature was added to the 1.1.5 release via a timeout parameter:
var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve(); // Aborts the $http request if it isn't finished.
Cancelling Angular $http Ajax with the timeout property doesn't work in Angular 1.3.15.
For those that cannot wait for this to be fixed I'm sharing a jQuery Ajax solution wrapped in Angular.
The solution involves two services:
HttpService (a wrapper around the jQuery Ajax function);
PendingRequestsService (tracks the pending/open Ajax requests)
Here goes the PendingRequestsService service:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('PendingRequestsService', ["$log", function ($log) {
var $this = this;
var pending = [];
$this.add = function (request) {
pending.push(request);
};
$this.remove = function (request) {
pending = _.filter(pending, function (p) {
return p.url !== request;
});
};
$this.cancelAll = function () {
angular.forEach(pending, function (p) {
p.xhr.abort();
p.deferred.reject();
});
pending.length = 0;
};
}]);})(window.angular);
The HttpService service:
(function (angular) {
'use strict';
var app = angular.module('app');
app.service('HttpService', ['$http', '$q', "$log", 'PendingRequestsService', function ($http, $q, $log, pendingRequests) {
this.post = function (url, params) {
var deferred = $q.defer();
var xhr = $.ASI.callMethod({
url: url,
data: params,
error: function() {
$log.log("ajax error");
}
});
pendingRequests.add({
url: url,
xhr: xhr,
deferred: deferred
});
xhr.done(function (data, textStatus, jqXhr) {
deferred.resolve(data);
})
.fail(function (jqXhr, textStatus, errorThrown) {
deferred.reject(errorThrown);
}).always(function (dataOrjqXhr, textStatus, jqXhrErrorThrown) {
//Once a request has failed or succeeded, remove it from the pending list
pendingRequests.remove(url);
});
return deferred.promise;
}
}]);
})(window.angular);
Later in your service when you are loading data you would use the HttpService instead of $http:
(function (angular) {
angular.module('app').service('dataService', ["HttpService", function (httpService) {
this.getResources = function (params) {
return httpService.post('/serverMethod', { param: params });
};
}]);
})(window.angular);
Later in your code you would like to load the data:
(function (angular) {
var app = angular.module('app');
app.controller('YourController', ["DataService", "PendingRequestsService", function (httpService, pendingRequestsService) {
dataService
.getResources(params)
.then(function (data) {
// do stuff
});
...
// later that day cancel requests
pendingRequestsService.cancelAll();
}]);
})(window.angular);
Cancelation of requests issued with $http is not supported with the current version of AngularJS. There is a pull request opened to add this capability but this PR wasn't reviewed yet so it is not clear if its going to make it into AngularJS core.
If you want to cancel pending requests on stateChangeStart with ui-router, you can use something like this:
// in service
var deferred = $q.defer();
var scope = this;
$http.get(URL, {timeout : deferred.promise, cancel : deferred}).success(function(data){
//do something
deferred.resolve(dataUsage);
}).error(function(){
deferred.reject();
});
return deferred.promise;
// in UIrouter config
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
//To cancel pending request when change state
angular.forEach($http.pendingRequests, function(request) {
if (request.cancel && request.timeout) {
request.cancel.resolve();
}
});
});
For some reason config.timeout doesn't work for me. I used this approach:
let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;
let httpPromise = $http.get(...);
$q.race({ cancelPromise, httpPromise })
.then(function (result) {
...
});
And cancelRequest.resolve() to cancel. Actually it doesn't not cancel a request but you don't get unnecessary response at least.
Hope this helps.
This enhances the accepted answer by decorating the $http service with an abort method as follows ...
'use strict';
angular.module('admin')
.config(["$provide", function ($provide) {
$provide.decorator('$http', ["$delegate", "$q", function ($delegate, $q) {
var getFn = $delegate.get;
var cancelerMap = {};
function getCancelerKey(method, url) {
var formattedMethod = method.toLowerCase();
var formattedUrl = encodeURI(url).toLowerCase().split("?")[0];
return formattedMethod + "~" + formattedUrl;
}
$delegate.get = function () {
var cancelerKey, canceler, method;
var args = [].slice.call(arguments);
var url = args[0];
var config = args[1] || {};
if (config.timeout == null) {
method = "GET";
cancelerKey = getCancelerKey(method, url);
canceler = $q.defer();
cancelerMap[cancelerKey] = canceler;
config.timeout = canceler.promise;
args[1] = config;
}
return getFn.apply(null, args);
};
$delegate.abort = function (request) {
console.log("aborting");
var cancelerKey, canceler;
cancelerKey = getCancelerKey(request.method, request.url);
canceler = cancelerMap[cancelerKey];
if (canceler != null) {
console.log("aborting", cancelerKey);
if (request.timeout != null && typeof request.timeout !== "number") {
canceler.resolve();
delete cancelerMap[cancelerKey];
}
}
};
return $delegate;
}]);
}]);
WHAT IS THIS CODE DOING?
To cancel a request a "promise" timeout must be set.
If no timeout is set on the HTTP request then the code adds a "promise" timeout.
(If a timeout is set already then nothing is changed).
However, to resolve the promise we need a handle on the "deferred".
We thus use a map so we can retrieve the "deferred" later.
When we call the abort method, the "deferred" is retrieved from the map and then we call the resolve method to cancel the http request.
Hope this helps someone.
LIMITATIONS
Currently this only works for $http.get but you can add code for $http.post and so on
HOW TO USE ...
You can then use it, for example, on state change, as follows ...
rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
angular.forEach($http.pendingRequests, function (request) {
$http.abort(request);
});
});
here is a version that handles multiple requests, also checks for cancelled status in callback to suppress errors in error block. (in Typescript)
controller level:
requests = new Map<string, ng.IDeferred<{}>>();
in my http get:
getSomething(): void {
let url = '/api/someaction';
this.cancel(url); // cancel if this url is in progress
var req = this.$q.defer();
this.requests.set(url, req);
let config: ng.IRequestShortcutConfig = {
params: { id: someId}
, timeout: req.promise // <--- promise to trigger cancellation
};
this.$http.post(url, this.getPayload(), config).then(
promiseValue => this.updateEditor(promiseValue.data as IEditor),
reason => {
// if legitimate exception, show error in UI
if (!this.isCancelled(req)) {
this.showError(url, reason)
}
},
).finally(() => { });
}
helper methods
cancel(url: string) {
this.requests.forEach((req,key) => {
if (key == url)
req.resolve('cancelled');
});
this.requests.delete(url);
}
isCancelled(req: ng.IDeferred<{}>) {
var p = req.promise as any; // as any because typings are missing $$state
return p.$$state && p.$$state.value == 'cancelled';
}
now looking at the network tab, i see that it works beatuifully. i called the method 4 times and only the last one went through.
You can add a custom function to the $http service using a "decorator" that would add the abort() function to your promises.
Here's some working code:
app.config(function($provide) {
$provide.decorator('$http', function $logDecorator($delegate, $q) {
$delegate.with_abort = function(options) {
let abort_defer = $q.defer();
let new_options = angular.copy(options);
new_options.timeout = abort_defer.promise;
let do_throw_error = false;
let http_promise = $delegate(new_options).then(
response => response,
error => {
if(do_throw_error) return $q.reject(error);
return $q(() => null); // prevent promise chain propagation
});
let real_then = http_promise.then;
let then_function = function () {
return mod_promise(real_then.apply(this, arguments));
};
function mod_promise(promise) {
promise.then = then_function;
promise.abort = (do_throw_error_param = false) => {
do_throw_error = do_throw_error_param;
abort_defer.resolve();
};
return promise;
}
return mod_promise(http_promise);
}
return $delegate;
});
});
This code uses angularjs's decorator functionality to add a with_abort() function to the $http service.
with_abort() uses $http timeout option that allows you to abort an http request.
The returned promise is modified to include an abort() function. It also has code to make sure that the abort() works even if you chain promises.
Here is an example of how you would use it:
// your original code
$http({ method: 'GET', url: '/names' }).then(names => {
do_something(names));
});
// new code with ability to abort
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
});
promise.abort(); // if you want to abort
By default when you call abort() the request gets canceled and none of the promise handlers run.
If you want your error handlers to be called pass true to abort(true).
In your error handler you can check if the "error" was due to an "abort" by checking the xhrStatus property. Here's an example:
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
function(names) {
do_something(names));
},
function(error) {
if (er.xhrStatus === "abort") return;
});
I am new to angularjs so I am struggling to pass a parameter to service method from controller.
My controller looks like this:
userControllers.controller('StartController', function(startService,$scope) {
var server='http://localhost:8080/terminal/1';
// Call the async method and then do stuff with what is returned inside our own then function
startService.async().then(function(d) {
$scope.message = d;
});
});
Service method:
myService.factory('startService', function($http) {
var startService = {
async: function () {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('http://localhost:8080/terminal/1').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response.headers('link'));
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
}
};
return startService;
});
And this code works fine. Now I want to pass variable 'server' from controller to be used in service instead of link. Any idea how to do that? How to use more than one variable in service function call?
Updated code is in between ** **
userControllers.controller('StartController', function(startService,$scope) {
var server='http://localhost:8080/terminal/1';
// Call the async method and then do stuff with what is returned inside our own then function
**startService.async(server).then(function(d) {**
$scope.message = d;
});
});
myService.factory('startService', function($http) {
var startService = {
async: **function (server) {**
// $http returns a promise, which has a then function, which also returns a promise
var promise = **$http.get(server)**.then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response.headers('link'));
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
}
};
return startService;
});