Difference between success,then along with resolve, promise,http in angularjs - javascript

In jquery when we fire ajax call ,when its successful,success function is called but, now in angular I have seen people using then as well as success.After googling a bit i found that then returns a promise.
If i want to load a route which has data that comes from a service I use resolve to execute http req before binding data into template but here in http I have used neither success/then.
My http request part :
movieserviceObj.getlist=function(){
return
$http({ url:'app/api/entertainment.php',data:$.param(dataString),method:'POST'});
Resolve part in config:
resolve:{movieslist:moviesController.getallMovies}
Controller :
var moviesController=app.controller('moviesController',function($scope,movieslist){
$scope.movies=movieslist.data.result
});
moviesController.getallMovies=function($q,$timeout,movieservice)
{
var defer=$q.defer();
$timeout(function(){
defer.resolve(movieservice.getlist());
},1000);
return defer.promise;
}
Now above code works completely fine but data-binding occurs after 1 sec as set in $timeout.My issue is that http request gets data within 1 sec, but then too I have to wait for 1sec.Is there any way,that as soon http req is completed it should return promise to resolve till then show loading bar ? Here in http i have not used success / then so how it works.Also even after successful http request how can I make sure that it has key named 'status' in response set to true, if true then only resolve or reject.

$http already returns a promise, so using then() makes much more sense here because:
you won't need to wait 1s every time
you won't risk not getting the data if the requests takes longer than 1s
Here's an example:
var moviesController = app.controller('moviesController',function($scope, movieservice){
movieservice.getlist().then(function(res){
$scope.movies = res.data;
}, function(err){
console.log('error:', err);
});
})
});
There are some nice point about the actual differences between then and success here: Angular HttpPromise: difference between `success`/`error` methods and `then`'s arguments.
Also even after successful http request how can I make sure that it has key named 'status' in response set to true, if true then only resolve or reject.
You'd have to modify your service to do that, similar to what you were doing in a controller:
movieserviceObj.getlist=function(){
var defer=$q.defer();
$http({
url:'app/api/entertainment.php',
data:$.param(dataString),
method:'POST'
}).then(function(res){
if (res.status === true) {
defer.resolve(res);
}
else {
defer.reject({error: 'Status not true'});
}
}, function(err) {
defer.reject(err);
})
return defer.promise;
}
Info about route resolve:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.

Related

Return promise from Angular service

I have a service that (when it's all said and done) updates a value on the database.
I would like to update the view scope based on the result (success/fail) but of course the http request used by the service is asynchronous so the return value is not immediately available and comes up undefined in the controller.
If I were making the http request inside the controller, I would update the scope inside the callback function, but because Im using a service, the scope's umm... scope(?) is not available to it.
Im thinking a Promise is what I need to be returning but perhaps there is something more simple.
SERVICE
.service('doStuff',function($http){
this.update = function(data) {
$http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
})
CONTROLLLER
/* service is injected into controller etc. */
var result = doStuff.update(data);
p(result); // undefined (as expected)
I figured since Im returning from the http callback, it would wait for the result to be available before returning but I guess Im missing something.
Since $http is always async, you cannot return anything in the call back function. It is as good as not returning anything.
What you need to do is you need to return the $http promise, and then handle the callback functions in your controller.
Service:
.service('doStuff', function($http) {
this.update = function(data) {
return $http.post('http://api.internet', data);
}
})
Controller:
doStuff.update(data).then(function(result){
p(result);
});
Foremost, you need to return the query itself. Looks like
this.update = function(data) {
return $http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
Next step, you need get out of the promise function.
doStuff.update(data)
.then(function(res) {
//someone if request is success
})
.catch(function(rej) {
//someone if request is reject
});

Prevent previous, yet longer-running $http request from returning after most recent request

I just came across a weird situation and wasn't able to find an answer for after some searching.
I have a textbox that I'm using to allow a user to type keywords to filter table data. I have an ng-change directive on the input element which will fire this code:
return $http.get(url).then(function (response) {
return response.data;
});
All of this has worked great, until our tester just found some undesirable behavior in IE11. Here is an example of typing "M-A-T-T" into the textbox in IE10:
As you can see, each subsequent request takes longer than the first so the result of the fourth and final request is the one the controller receives.
Here is the same example of typing "M-A-T-T" into the textbox in IE11.
Inexplicably, the second request takes almost 2 seconds to complete which is after the fourth and final request completed. This results in the results from the "MA" request displaying when the user is expecting the results of the "MATT" request (which is what is currently in their textbox).
How can this be dealt with in Angular? Thanks in advance.
UPDATE
Based on frosty's response, I've implemented the following (in addition to bouncing) which works great:
var cancelDeferred;
var getUsers = function (criteria) {
if (cancelDeferred) {
cancelDeferred.resolve();
}
cancelDeferred = $q.defer();
return $http.get(url, { timeout: cancelDeferred.promise }).then(function (response) {
cancelDeferred = undefined;
return response.data;
});
};
The main challenge, actually, was handling errors when this method is called. The timeout returns an error just like a 500 would return an error. I want to ignore the timeouts and handle the actual errors. Here is my solution for that:
function onError(data, status) {
if (data.data !== null && data.status !== 0) {
//handle error
}
}
Now I'll try to figure out if there is a way to implement this promise-timing-out globally instead of having to alter a ton of $http.get() calls...
In the documentation for $http, it talks about a config object that you can pass as a second argument to the $http.get call, like this:
$http.get(url, config);
In that config, it talks about a "timeout" property that you can set. If you set that property as a Promise, then if you resolve the promise, it will abort the request. Check out the documentation for the description there.
Even if you use a debounce, you will want to use this "timeout" to abort the request.
https://docs.angularjs.org/api/ng/service/$http
So you would do something like this:
var cancelDeferred;
function makeRequest(){
if(cancelDeferred) cancelDeferred.resolve(); //when this gets resolved, the previous request will abort.
cancelDeferred = $q.defer();
return $http.get(url,{timeout:cancelDeferred.promise})
.then(function(res){
cancelDeferred = undefined;
return res;
});
}
So, you pass a promise to the .get request, inside the config object as the "timeout" property. If you resolve this deferred before the response comes back, it will abort the request.

The promise of a promise again (Angular JS)

Updated with HTTP and initial code based on requests/Please look at the bottom of the post:
I've been posting several questions on my AngularJS learning curve of late and the SO community has been fantastic. I've been a traditional C programmer when I used to program and have recently started writing my own ionic/Angular JS app. I'm struggling with the promise version of traditional async calls when it comes to converting a custom function to a promise. I don't think I really understood and I find various examples very contrived. I'd appreciate some help. I have some code which is not working, and I have some conceptual questions:
Let's take this simple function:
angular.module('zmApp.controllers').service('ZMDataModel', function() { return { getMonitors: function () { return monitors; } }
getMonitors is a simple function that basically returns an array of monitors. But here is the rub: When the app first starts, I call an http factory that does an http get and goes about populating this monitor list. This http factory is different from this service but invokes a setMonitor method in this service to populate the array. When the array is populated, a variable called 'monitorsLoaded' is set to 1. When this variable is set to 1, I know for sure monitors is loaded.
Now, I have a view with a controller called "MontageCtrl". I want to wait for the monitors to load before I show the view. In a previous post, one person suggested I use route resolve, but I had to first convert my getMonitors to a promise. So here is what I did:
angular.module('zmApp.controllers').service('ZMDataModel', function($q) {
getMonitors: function () {
var _deferred = $q.defer();
if (monitorsLoaded!=0)
{
console.log ("**** RETURNING MONITORS *****");
_deferred.resolve(monitors);
}
console.log ("*** RETURNING PROMISE ***");
return _deferred.promise;
},
Next up, in app.js I connected the route as follows:
.state('app.montage', {
data: {requireLogin:false},
resolve: {
message: function(ZMDataModel)
{
console.log ("Inside app.montage resolve");
return ZMDataModel.getMonitors();
}
},
Finally I modified my controller to grab the promise as such:
angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', function($scope,$rootScope, ZMHttpFactory, ZMDataModel,message) {
//var monsize =3;
console.log ("********* Inside Montage Ctrl");
It seems based on logs, I never go inside Montage Ctrl. Route resolve seems to be waiting for ever, whereas my logs are showing that after a while, monitorLoaded is being set to 1.
I have several conceptual questions:
a) In function getMonitors, which I crafted as per examples, why do people return a _deferred.promise but only assign a _deferred.resolve? (i.e. why not return it too?). Does it automatically return?
b) I noticed that if I moved var _deferred definition to my service and out of its sub function, it did work, but the next view that had the same route dependency did not. I'm very confused.
c) Finally I ready somewhere that there is a distinction between a service and a factory when it comes to route resolve as a service is only instantiated once. I am also very confused as in some route resolve examples people use when, and I am using .state.
At this stage, I'm deep into my own confusion. Can someone help clarify? All I really want is for various views to wait till monitorsLoaded is 1. And I want to do it via route resolves and promises, so I get the hang of promises once and for all.
Added: Here is the HTTP factory code as well as the app.run code that calls this when the app first starts. FYI, the http factory works well - the problems started when I crafted ZMDataModel - I wanted this to be a central data repository for all controllers to use -- so they did not have to call HTTP Factory each time to access data, and I could control when HTTP factory needs to be called
angular.module('zmApp.controllers').factory('ZMHttpFactory', ['$http', '$rootScope','$ionicLoading', '$ionicPopup','$timeout','ZMDataModel',
function($http, $rootScope, $ionicLoading, $ionicPopup, $timeout,ZMDataModel) {
return {
getMonitors: function() {
var monitors = [];
var apiurl = ZMDataModel.getLogin().apiurl;
var myurl = apiurl+"/monitors.json";
return $http({
url: myurl,
method: 'get'
}) //http
.then(function(response) {
var data = response.data;
//console.log("****YAY" + JSON.stringify(data));
// $rootScope.$broadcast ('handleZoneMinderMonitorsUpdate',monitors);
$ionicLoading.hide();
ZMDataModel.setMonitors(data.monitors);
ZMDataModel.setMonitorsLoaded(1);
//monitors = data.monitors;
return ZMDataModel.getMonitors();
},
function (result)
{
console.log ("**** Error in HTTP");
$ionicLoading.hide();
ZMDataModel.setMonitorsLoaded(1);
//$ionicPopup.alert ({title: "Error", template:"Error retrieving Monitors. \nPlease check if your Settings are correct. "});
return ZMDataModel.getMonitors();
}
); //then
}, //getMonitors
And here is the code in app.run that first calls this:
.run(function($ionicPlatform, $ionicPopup, $rootScope, $state,ZMDataModel, ZMHttpFactory)
{
ZMDataModel.init();
var loginData = ZMDataModel.getLogin();
if ( loginData.username && loginData.password && loginData.url && loginData.apiurl)
{
console.log ("VALID CREDENTIALS. Grabbing Monitors");
// this calls http factory getMonitors that eventually populated the ZMDataModel
// monitors array and sets monitorsLoaded to 1
ZMHttpFactory.getMonitors();
}
}
I finally solved all the problems. There were various issues with my initial attempts. My final resolved solution is here Am I returning this promise correctly?
The learnings:
a) Separating the HTTP get into a factory and the data model into another service was unnecessarily complicating life. But that separation was not the problem. Infact, the way the promise was coded above, on first run, if monitorsLoaded was 0, it would simply return the deferred promise and there was no ".success" or similar construct for me to get into the resolve code block again.
b) The biggest thing that was making me run around in loops was deferring or rejecting was simply setting a state. the return always has to be the promise - and it would return the state you set. I assumed return d.promise always means returning "in progress".

preload images after $http request with angular service promise

i need to preload images after an $http service.
Now in my application i'm using promise/resolve method to preload data with service.
here my service:
angular.module('davelab.services', [])
.factory('srvProjects', ['$http', function ($http) {
var API = '/backend/api/';
var sdo = {
getProjects: function() {
var promise = $http({ method: 'GET', url: API + 'get_posts/' }).success(function(data, status, headers, config) {
return data;
});
return promise;
}
};
return sdo;
}]);
});
and here the route part:
$routeProvider.when('/projects', {
templateUrl: 'app/partials/list-projects.html',
controller: 'ProjectsCtrl',
resolve: {
projects: function(srvProjects) {
return srvProjects.getProjects();
}
}
});
it works well with data but for the images retrieved from api request it doesn't work.
How can I preload images in cache before show the view with this method?
i have to iterate through data into success callback or somewhere else?
thanks.
Take the URL's you're receiving from your original request, but instead of resolving the promise right away, create a new promise for each image. Preload the image using any method you prefer (but it needs a callback to resolve its promise) and resolve each promise after the image is loaded. You should end up with an array of image promises. and then return $q.all(imagePromises) from your resolve. Check out the docs for $q.
$q.all will return a promise that is only resolved when all promises passed into it are resolved.
I haven't tested it, but I think you might be able to just use $http on each image URL and it will cache it for you and give you a nice promise to pass into $q.all. So basically you'll have an array of $http calls.
You can use like this in your controller
$scope.preloader = true
then you can use angular promise of
.then(function(result){
//your code goes here
$scope.preloader = false;
}
Also you have $scope.preloader= false in you error block
If you have multiple Ajax calls at a same time parallely then use
$scope.preloader = 0;
on stating the request use
$scope.preloader++;
and when done use
$scope.preloader--;
You can have more clear answer here
Showing Spinner GIF during $http request in angular

Retry request with $http interceptor

I'm sure there is an easy way to do what I want, I just cant wrap my head around it. How can I get the http interceptor in angular to retry a request if it fails? I imagine I would have to build some sort of promise in the request right? Then in the response I would have to check whether the response was an error and if so, do the promise? How is that done? I have been trying to adapt the example here: http://docs.angularjs.org/api/ng.$http
The reason I am trying to use an interceptor is because I need to add the token and a few other things to the request url, as well as some things to handle xdr's.
Here's a $http interceptor that (immediately) replays timed out request(s) (i.e. response status 0). There were two non-obvious (to me!) elements to constructing this example:
How to call $http from within the interceptor - simply adding $http to the dependency list didn't work as angular complains of a circular dependency
How to reference the original request from the response object in order to retry it
This answer addresses both topics but is more involved so I include a simplified version below.
joinApp.factory('httpResponseErrorInterceptor',function($q, $injector) {
return {
'responseError': function(response) {
if (response.status === 0) {
// should retry
var $http = $injector.get('$http');
return $http(response.config);
}
// give up
return $q.reject(response);
}
};
});
joinApp.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
In your actual implementation, you would likely want more sophisticated http response code processing, a limit to the number of times you retry, etc.
For the sake of completeness, here is a version of mygzi's answer with retry delay:
.factory('httpResponseErrorInterceptor', ['$injector', '$q', '$timeout', function($injector, $q, $timeout) {
return {
'responseError': function(response) {
if (response.status === 0) {
return $timeout(function() {
var $http = $injector.get('$http');
return $http(response.config);
}, 15000);
}
return $q.reject(response);
}
};
}])
.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
$timeout returns a promise that is completed with what is returned from the function parameter, so we can conveniently just return the $http call wrapped in $timeout.
I've done this for an app I wrote before. To do the retry wrap the $http usage in a function (or preferably in a service so that you can reuse it easily). If the request fails, call the function again.
The trick is then to pass the promise object along with each request. If you create a new promise with each request then it won't match the one you returned to the original caller, so the original caller won't get his promise resolve once the request passes.
So it is something like this (note that the defer object is passed along in each retry):
app.service('HttpService', ['$q', function($q) {
this.makeRequest = _makeRequest;
function _makeRequest(url, data, deffered) {
// We want to keep the same promise for each request, so we don't loose track
if (deferred === undefined) {
deferred = $q.defer();
}
// Now make the request
$http({...}).success(...).error(
function(){
// If some condition
_makeRequest(url, data, deffered);
}
)
// Lastly return the promise
return deferred.promise;
}
}])

Categories