I am trying to use resolve in a $routeProvider to display the new route only when a $http request is finished. If the request is successful, the promise resulting from the $http.post() is resolved and the view is rendered. But if the request fails (timeout or internal error for eg.), the promise is never resolved and the the view is never rendered. How can I deal with request failure using resolve ?
The most important parts of the code is bellow :
app.js
$routeProvider.when('/warrantyResult', {
templateUrl : 'partials/warranty-result.html',
controller : 'WarrantyResultCtrl',
resolve : {
response : [ 'Warranty', function(Warranty) {
return Warranty.sendRequest();
} ]
}
});
controllers.js
angular.module('adocDemo.controllers', []).controller('HomeCtrl', [ '$scope', function($scope) {
} ]).controller('WarrantyCtrl', [ '$scope', '$http', '$location', 'Warranty', function($scope, $http, $location, Warranty) {
$scope.submitWarranty = function() {
$scope.loading = true;
Warranty.setRequestData($scope.data);
$location.path('/warrantyResult');
};
} ]).controller('WarrantyResultCtrl', [ '$scope', 'Warranty', function($scope, Warranty) {
$scope.request = Warranty.getRequestData();
$scope.response = Warranty.getResponseData();
} ]);
services.js
angular.module('adocDemo.services', []).factory('Warranty', [ '$http', '$timeout', function($http, $timeout) {
/**
* This service is used to query the Warranty Webmethod. The sendRequest
* method is automaticcaly called when the user is redirected to
* /warrantyResult route.
*/
var isDataSet = false;
var requestData = undefined;
var responseData = undefined;
return {
setRequestData : function(data) {
//Setting the data
isDataSet = true;
},
getRequestData : function() {
return requestData;
},
sendRequest : function(data) {
if(isDataSet) {
var request = $http.post('url/to/webservice', requestData);
request.success(function(data) {
responseData = data;
});
return request;
}
},
getResponseData : function() {
return responseData;
}
};
} ]);
I know i could use a promise around the $http call and resolve it even if the request is a failure, but I'm wondering if there is a simpler solution.
Thanks for reading and, hopefully, helping :)
I think the only way to do it from resolve is to manually resolve the promise returned by Warranty.sendRequest and rewrap it in a new promise:
resolve : {
response : [ 'Warranty' '$q', function(Warranty, $q) {
var dfd = $q.defer();
Warranty.sendRequest().then(function(result) {
dfd.resolve({ success: true, result : result });
}, function(error) {
dfd.resolve({ success : false, reason : error });
});
return dfd.promise;
} ]
}
In WarrantyResultCtrl, you could check if an error occurred and generate a redirect.
EDIT: much cleaner solution:
// WarrantyCtrl
$scope.$on('$routeChangeError', function() {
// handle the error
});
$scope.submitWarranty = function() {
$scope.loading = true;
Warranty.setRequestData($scope.data);
$location.path('/warrantyResult');
};
(plunker demo)
What I have found is that the controller is not fired at all if the Promise is rejected, and your view is never rendered--same as you.
What I also discovered is that if you handle the Promise with a .then() in your $routeProvider's resolve, the .then() will return a new Promise that is resolved and your controller is fired after all, albeit without the data you are expecting.
For example:
$routeProvider.when('/warrantyResult', {
templateUrl : 'partials/warranty-result.html',
controller : 'WarrantyResultCtrl',
resolve : {
response : [ 'Warranty', function(Warranty) {
return Warranty.sendRequest()
.then(null, function(errorData) {
// Log an error here?
// Or do something with the error data?
});
}]
}
});
Now in your controller you will want to check whether response is undefined. If it is undefined then you'll know that the call to Warranty.sendRequest() failed, and you can act accordingly.
For what it's worth, I did not go this route. Instead, I injected the $location service into the resolve handler and redirected to an error page if the $http call gets rejected.
UPDATE
Just noticed that in your controller you are injecting the Warranty service when you should instead be injecting the response that you defined in your resolve. That will prevent your view from rendering until the Promise returned from Warranty.sendRequest() is resolved.
After deep searching, I could not find a solution for this problem.
I decided to drop the resolve statment in my route definition and I use the following snippet of code in the WarrantyCtrl.
$scope.submitWarranty = function() {
$scope.formatUserData();
if ($scope.verifyUserData()) {
$scope.loading = true;
Warranty.setRequestData($scope.data);
Warranty.sendRequest().success(function() {
$location.path('/warrantyResult');
}).error(function() {
$location.path('/webserviceError');
});
}
};
Not very clever, but works as intented ... If someone still have the solution for the original problem, I would be very pleased to read it !
Related
Hello my code for service is:
angular.module('lucho1App').factory('ApiExample', ['$http', '$q', function($http, $q) {
return {
promiseToHaveData: function() {
return $http.get('http://data.colorado.gov/resource/4ykn-tg5h.json').then(function(response) {
var result = response.data;
console.log(result);
return result;
}, function(response) {
console.log('Error has ocurred');
});
}
};
}]);
and my controller is:
angular.module('lucho1App')
.controller('MainCtrl',['$scope', 'ApiExample',function MainCtrl($scope,ApiExample) {
$scope.apiExampl=ApiExample.promiseToHaveData();
console.log($scope.apiExampl);
}]);
the console.log in the service shows output which means that my request is successful but in the controller $scope.apiExampl is undefined where is my mistake?
A promise is asynchrnous, so it will be resolved later and the function in the then will be called at that very moment. The return in the callback permit to change the object passed to the next then call since promise are chainable object.
First you must return the promise object in your service :
return $http.get(...);
Second change your controller like this :
ApiExample.promiseToHaveData().then(function(data){
$scope.apiExampl=data;
});
note that here i do =data because in your service you already extracted the data object from the response
You need to return promise
promiseToHaveData: function() {
return $http.get('http://data.colorado.gov/resource/4ykn-tg5h.json');
}
And handle the success handler in controller
ApiExample.promiseToHaveData().success (function(response) {
$scope.apiExampl = response.data;
console.log($scope.apiExampl);
});
I am new in angular $q service.I'm using $http with angular $q service for implementing asynchronous requests. Here in below is my codes which I can't get the result of backend api. (json)
Services.js :
.service('httpService', function($q, $http, $timeout) {
var asyncRequest = function(url) {
return $http.get(url)
.then(function(response) {
//res is the index of an array in php, which will be encoded.
return response.res;
}, function(response) {
// something went wrong
return $q.reject(response.res);
});
};
return {
asyncRequest : asyncRequest
};
});
Controller.js :
var result = httpService.test(url)
.then(function(data) {
// Line below gives me "undefined"
console.log(data);
}, function(error) {
alert("Error...!");
});
The mentioned line, gives me undefined. (Of course, I can write console.log(data) in main function, But it's not a good practice, because I want to return result to controller)
About my implementation of $q service, is there any easier way?
Any idea would be greatly appreciated.
You should not use $q in this instance, as $http already returns a promise. Using to 2 together in inefficient. ($q is of use if you are using a non-angular async function, such as a Geo lookup).
Services.js :
.service('httpService', function($http, $timeout) {
var asyncRequest = function(url) {
return $http.get(url)
};
return {
asyncRequest : asyncRequest
};
});
Controller.js :
var result = httpService.asyncRequest(url)
.then(function(res) {
console.log(res.data);
}, function(error) {
alert("Error...!");
});
First thing is that you are using factory style instead of service. service is just a function where methods are defined on this reference .
I think you don't need to use .then in service just return the promise returned by $http
app.service('httpService', function($q, $http, $timeout) {
this.asyncRequest = function(url) {
return $http.get(url);
};
});
And in controller
var result = httpService.test(url)
.then(function(res) {
// Line below gives me "undefined"
console.log(res.data);
}, function(error) {
alert("Error...!");
});
I think you are using the syntax for at factory on your service.
.service('httpService', function($q, $http, $timeout) {
this.asyncRequest = function(url) {};
});
or
.factory('httpService', function($q, $http, $timeout) {
return {asyncRequest: function(url) {}};
});
The response is already rejected in the mentioned line. You don't need to reject anything else. So you don't need to $q.
First you already return a promise. You can handle it in the controller with adding success() and error() delegates of the $http promise.
Second, this is async operation. And you can't return a response from the success callback like jQuery.ajax(). This is not synch call, this is asynch call and you have to use callbacks. Your mistake is here. Just return promise and handle it in controller when the response will has been resolved or rejected.
So your controller code can be like this:
httpService.asyncRequest({
...
}).success(function(successfulResponse) {
...
}).error(function(failedResponse) {
...
});
.service('httpService', function($q, $http, $timeout) {
var asyncRequest = function(url) {
var defer = $q.defer();
return $http.get(url)
.then(function(response) {
//res is the index of an array in php, which will be encoded.
defer.resolve(response);
}, function(response) {
// something went wrong
defer.reject(response.res);
});
return defer.promise;
};
return {
asyncRequest : asyncRequest
};
});
you should return promise from your object like this
I am using AngularJS to call an http service that returns some opening times in an object. I don't understand why, in my controller, the console.log is printed 4 times, instead of one time. Can anyone explain this to me?
Here is my service/factory code:
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
var deferredTime = $q.defer();
return {
GetDealerLocationTimeList: function(websiteId) {
return $http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
deferredTime.resolve(response.data);
dealerLocationTimeList.push(response.data);
return deferredTime.promise;
}, function(error) {
deferredTime.reject(response);
return deferredTime.promise;
});
}
}
}]);
Here is my controller code that is calling the service:
var promise = BookingFactory.GetDealerLocationTimeList(website_id);
promise.then(
function(da) {
$scope.dealerLocationTimeList = da;
console.log($scope.dealerLocationTimeList);
},
function(error) {
$log.error('failure loading dealer associates', error);
}
);
There are many mistakes in this code ><
If you want to use deferred, then this should be the code:
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
return {
GetDealerLocationTimeList: function(websiteId) {
var deferredTime = $q.defer(); // deferred should be created each time when a function is called. It can only be consumed (resolved/rejected) once.
/* return - don't need to return when you already creating a new deferred*/
$http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
deferredTime.resolve(response.data);
// dealerLocationTimeList.push(response.data);
}, function(error) {
deferredTime.reject(error); // it should be 'error' here because your function argument name says so...
});
return deferredTime.promise; // promise is returned as soon as after you call the function, not when the function returns
}
}
}]);
But it is a better practice to return the promise if your inner function is a promise itself (like $http.get)
myApp.factory('BookingFactory', ['$http', '$q', function($http, $q) {
return {
GetDealerLocationTimeList: function(websiteId) {
// no need to create new deferred anymore because we are returning the promise in $http.get
return $http.get('/d/GetDealerLocationTimes?website_id=' + websiteId)
.then(function(response) {
// dealerLocationTimeList.push(response.data);
return response.data; // return the data for the resolve part will make it available when the outer promise resolve
}/* this whole part should be omitted if we are not doing any processing to error before returning it (thanks #Bergi)
, function(error) {
return $q.reject(error); // use $q.reject to make this available in the reject handler of outer promise
}*/);
// no need to return promise here anymore
}
}
}]);
You can see I've also commented your dealerLocationTimeList.push(response.data). In this case you should push the data into your scope variable on the outer layer (in the promise.then), because dealerLocationTimeList is not available in the factory.
promise.then(
function(da) {
// you might want to do an isArray check here, or make sure it is an array all the time
$scope.dealerLocationTimeList.push(da);
console.log($scope.dealerLocationTimeList);
},
...
);
Is it possible to pass a promise to a UI.Router $state from an outside controller (e.g. the controller that triggered the state)?
I know that $state.go() returns a promise; is it possible to override that with your own promise resolve this promise directly yourself or resolve it using a new promise?
Also, the documentation says the promise returned by $state.go() can be rejected with another promise (indicated by transition superseded), but I can't find anywhere that indicates how this can be done from within the state itself.
For example, in the code below, I would like to be able to wait for the user to click on a button ($scope.buttonClicked()) before continuing on to doSomethingElse().
I know that I can emit an event, but since promises are baked into Angular so deeply, I wondered if there was a way to do this through promise.resolve/promise.reject.
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: ['$state', '$scope', '$q', function ($state, $scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
}]
});
}])
.controller('mainCtrl', ['$state', function ($state) {
$state.go('myState')
.then(doSomethingElse)
}]);
Update
I have accepted #blint's answer as it has got me closest to what I wanted. Below is some code that fleshes out this answer's idea a bit more. I don't think the way I have written this is a very elegant solution and I am happy if someone can suggest a better way to resolve promises from a triggered state.
The solution I've chosen is to chain your promises as you normally would in your controller, but leave a $scope.next() method (or something similar) attached to that scope that resolves/rejects the promise. Since the state can inherit the calling controller's scope, it will be able to invoke that method directly and thus resolve/reject the promise. Here is how it might work:
First, set up your states with buttons/controllers that call a $scope.next() method:
.config(function ($stateProvider) {
$stateProvider
.state('selectLanguage', {
template: '<p>Select language for app: \
<select ng-model="user.language" ng-options="language.label for language in languages">\
<option value="">Please select</option>\
</select>\
<button ng-click="next()">Next</button>\
</p>',
controller: function ($scope) {
$scope.languages = [
{label: 'Deutch', value: 'de'},
{label: 'English', value: 'en'},
{label: 'Français', value: 'fr'},
{label: 'Error', value: null}
];
}
})
.state('getUserInfo', {
template: '<p>Name: <input ng-model="user.name" /><br />\
Email: <input ng-model="user.email" /><br />\
<button ng-click="next()">Next</button>\
</p>'
})
.state('mainMenu', {
template: '<p>The main menu for {{user.name}} is in {{user.language.label}}</p>'
})
.state('error', {
template: '<p>There was an error</p>'
});
})
Next, you set up your controller. In this case, I'm using a local service method, user.loadFromLocalStorage() to get the ball rolling (it returns a promise), but any promise will do. In this workflow, if the $scope.user is missing anything, it will progressively get populated using states. If it is fully populated, it skips right to the main menu. If elements are left empty or are in an invalid state, you get taken to an error view.
.controller('mainCtrl', function ($scope, $state, $q, User) {
$scope.user = new User();
$scope.user.loadFromLocalStorage()
.then(function () {
var deferred;
if ($scope.user.language === null) {
deferred = $q.defer();
$state.go('selectLanguage');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.language === null) {
return deferred.reject('Language not selected somehow');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
var deferred;
if ($scope.user.name === null || $scope.user.email === null) {
deferred = $q.defer();
$state.go('getUserInfo');
$scope.next = function () {
$scope.next = undefined;
if ($scope.user.name === null || $scope.user.email === null) {
return deferred.reject('Could not get user name or email');
}
deferred.resolve();
};
return deferred.promise;
}
})
.then(function () {
$state.go('mainMenu');
})
.catch(function (err) {
$state.go('error', err);
});
});
This is pretty verbose and not yet very DRY, but it shows the overall intention of asynchronous flow control using promises.
The purpose of promises is to guarantee a result... or handle a failure. Promises can be chained, returned in functions and thus extended.
You would have no interest in "overriding" a promise. What you can do, however:
Handle the failure case. Here's the example from the docs:
promiseB = promiseA.then(function(result) {
// success: do something and resolve promiseB
// with the old or a new result
return result;
}, function(reason) {
// error: handle the error if possible and
// resolve promiseB with newPromiseOrValue,
// otherwise forward the rejection to promiseB
if (canHandle(reason)) {
// handle the error and recover
return newPromiseOrValue;
}
return $q.reject(reason);
});
Append a new asynchronous operation in the promise chain. You can combine promises. If a method called in the chain returns a promise, the parent promised will wall the rest of the chain once the new promise is resolved.
Here's the pattern you might be looking for:
angular.module('APP', ['ui.router'])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('myState', {
template: '<p>myState</p>',
controller: 'myCtrl'
});
}])
.controller('myCtrl', ['$scope', '$state', '$q', '$http', 'someAsyncServiceWithCallback',
function ($scope, $state, $q, $http, myService) {
$scope.buttonClicked = function () {
$state.go('myState')
.then(function () {
// You can return a promise...
// From a method that returns a promise
// return $http.get('/myURL');
// Or from an old-school method taking a callback:
var deferred = $q.defer();
myService(function(data) {
deferred.resolve(data);
});
return deferred.promise;
},
function () {
console.log("$state.go() failed :(");
});
};
}]);
Perhaps one way of achieving this would be to return your promise from the state's resolve
resolve: {
myResolve: function($scope, $q) {
var deferred = $q.defer();
$scope.buttonClicked = function () {
deferred.resolve();
}
return deferred.promise;
}
}
There is also an example in resolve docs that may be of interest
// Another promise example. If you need to do some
// processing of the result, use .then, and your
// promise is chained in for free. This is another
// typical use case of resolve.
promiseObj2: function($http){
return $http({method: 'GET', url: '/someUrl'})
.then (function (data) {
return doSomeStuffFirst(data);
});
},
I am using $http.post to get the data from node.js server. I want to handle the delay.
I had added timeout as $http.defaults.timeout = 100; and expected to console.log the delay in error but it is not working.
Example:
$http.defaults.timeout = 100;
$http.post(url, data).success(function(result) {
callback(result);
}).error(function(error) {
console.log("error");
});
I am new to AngularJS. Any help will be grateful.
The $timeout returns promise. The $http.post returns promise as well.
So I would use $q.all. Documents
Reference
$q.all([promise, …]) → newPromise
newPromise will resolve once all the given promises have
been resolved.
We can create some factory (or if you want to change it use can use provider):
.factory('delay', ['$q', '$timeout',
function($q, $timeout) {
return {
start: function() {
var deferred = $q.defer();
$timeout(deferred.resolve, 100);
return deferred.promise;
}
};
}
]);
and now in controller we can write something like:
$q.all([delay.start(), $http.post(url, data)]).then(
function(results) {
// .....
}, function(error) {
// ....
});
So you get response only if timeout stopped no matter how quick we get response from $http.post
AngularJS $http accepts timeout as a one of the request parameters (more here)
Please have a look at this post which explains how to implement the timeout functionality:
$http.post(url, { timeout: 100 })
.success(success)
.error(error);
Success as well as error functions accepts several parameters: function(data, status, headers, config). When you get a timeout error, error handler will be executed and its status will be 0.
I hope that will help.
Check this one :
angular.module('MyApp', [])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.timeout = 5000;
}]);