I'm getting crazy with this since a couple of hours.
I have an angular service factory to get addresses from my API:
App.factory('storesService', ['$http', '$q', 'endpoint', function ($http, $q, endpoint) {
var deferred = $q.defer();
return {
addresses: function (store_id) {
$http.get(endpoint.store.addresses, {
params: {
id: store_id
}
})
.success(function (data) {
console.log('Data from API:' + data);
deferred.resolve(data);
})
.error(function () {
deferred.reject();
});
return deferred.promise;
}
};
}]);
This service is used in my controller to get addresses of a specific store:
$scope.loadAddresses = function (store_id) {
var load = storesService.addresses(store_id);
load.then(function (data) {
console.log('Deferred data:' + data);
$scope.addresses = data.addresses;
});
};
In my view I have the ng-init="loadAddresses(store_id)", store_id is a right value.
I'm also using angular-xeditable (select-local) to manage my store selection.
I added onaftersave='storeChanged(store.id)' in my view to get the store id selected by the user and it return correctly the new id.
my storeChanged function is very easy, it basically run a new request to the API:
$scope.storeChanged = function (store_id) {
$scope.loadAddresses(store_id);
};
What happen:
At the beginning, with ng-init I see correctly the console.log, first the one from the service and then the one from the controller.
Once I select another store from my select I first see the console.log from the controller and then the one from the service.
Basically the data in the controller is not updated and I can not understand why it happen...
You defined your deferred globally in the service, so there is only one global promise. Because a promise can only be resolved or rejected once, it will stay resolved/rejected forever after your first http call. To fix simply move the line var deferred = $q.defer(); into your service function:
App.factory('storesService', ['$http', '$q', 'endpoint', function ($http, $q, endpoint) {
return {
addresses: function (store_id) {
var deferred = $q.defer();
$http.get(endpoint.store.addresses, {
params: {
id: store_id
}
})
.success(function (data) {
console.log('Data from API:' + data);
deferred.resolve(data);
})
.error(function () {
deferred.reject();
});
return deferred.promise;
}
};
}]);
You've created one defer for potentially many requests. The first time you make a request it will work, but after than it will instantly return, as the one promise you've set up has already resolved. Patterns smilar to this can be very useful for caching, actually.
$http already returns a promise. You don't need to go out of your way to use $q
App.factory('storesService', ['$http', 'endpoint', function ($http, endpoint) {
return {
addresses: function (store_id) {
return $http.get(endpoint.store.addresses, {
params: {
id: store_id
}
}).then(function(response){
//Chain an extra promise here to clean up the response to just return the data.
return response.data;
})
}
};
}]);
You're trying to re-resolve a promise, which you can't do. You only create one deferred that every request uses. That should be inside of the addresses function so that a new one is created for each request, but you don't need it anyway because $http creates and returns a promise already. You need to return the promise from $http rather than creating a new one. See this post for a better understanding: What is the explicit promise construction antipattern and how do I avoid it?
addresses: function (store_id) {
return $http.get(endpoint.store.addresses, {
params: {
id: store_id
}
}).then(function(resp) {
console.log('Data from API:' + resp.data);
return resp.data;
});
}
Related
I have a controller that performs a http request.
This request can take anywhere between 2 seconds to 4 minutes to return some data .
I have added a button, that users should click to cancel the request if searches take too long to complete.
Controller:
$scope.search = function() {
myFactory.getResults()
.then(function(data) {
// some logic
}, function(error) {
// some logic
});
}
Service:
var myFactory = function($http, $q) {
return {
getResults: function(data) {
var deffered = $q.dafer();
var content = $http.get('someURL', {
data: {},
responseType: json
)}
deffered.resolve(content);
returned deffered.promise;
}
}
}
Button click:
$scope.cancelGetResults = function() {
// some code to cancel myFactory.getResults() promise
}
How can I implement a button click to cancel the myFactory.getResults() promise?
The question uses deferred antipattern which usually should be avoided, but it fits the cancellation case:
getResults: function(data) {
var deffered = $q.defer();
$http.get('someURL', {
data: {},
responseType: json
}).then(deffered.resolve, deferred.reject);
deffered.promise.cancel = function () {
deferred.reject('CANCELLED')
};
returned deffered.promise;
}
getResult is a service in which we are implementing cancellation.
getResult = function(){
var deferred = $q.defer();
$http.get(url).success(function(result){
deffered.resolve(result);
}).error(function(){
deffered.reject('Error is occured!');
});
return deferred.promise;
};
where url variable is used in place of any Restful API url. You can use it with given code.
getResult().then(function (result) { console.log(result); };
You could use .resolve() method which should be available.
Pass the promise in the controller to the variable.
Create e.g. cancel method which takes the promise as an argument in you factory. Then call this method in the cancelGetResults() function in the controller.
In the cancel method you just call .resolve on the passed promise.
This should actually do.
https://www.bennadel.com/blog/2731-canceling-a-promise-in-angularjs.htm
I'm fairly new to AngularJS and have just begun to grasp many of the concepts I especially like the MVC design pattern. But I am having a difficult time implementing the Service layer in my application.
What I am finding is that after my Controller calls a method within the service, it continues with code execution before the service returns the data; so that by the time the service does return the data, it isn't of any use to the controller.
To give a better example of what I'm saying, here is the code:
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
InsightApp.controller("ChartSelectionController", GetAvailableCharts);
// 2nd Controller calls the Service
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections();
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
All the examples I've found seem to be constructed as I have here or some slight variation; so I am not certain what I should be doing to ensure asynchronous execution.
In the Javascript world, I'd move the service to the inside of the Controller to make certain it executes async; but I don't how to make that happen in Angular. Also, it would be counter intuitive against the angular injection to do that anyway.
So what is the proper way to do this?
http return a promise not the data, so in your factory your returning the $http promise and can use it like a promise with then, catch, finally method.
see: http://blog.ninja-squad.com/2015/05/28/angularjs-promises/
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections()
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
EDIT pass params to model service:
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (yourParameter) {
console.log(yourParameter);
return $http.get('Home/GetSalesData')
.then(function (result) {
return result.data;
});
}
};
});
and then :
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections('only pie one')
.then(function(res) {
// execute when you have the data
})
.catch(function(err) {
// execute if you have an error in your http call
});
You should proceed like this :
DataService.GetChartSelections().then(function (data) {
workWithData(data);
}
Actually $http.get returns a Promise. You can call the method then to handle the success or failure of your Promise
Should it not be like this, when your $http returns a promise or you pass a callback.
With passing callback as a param.
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function (workWithData) {
return $http.get('Home/GetSalesData')
.then(function (result) {
workWithData(result.data);
});
}
};
});
Controller code:
InsightApp.controller("DataController", function($scope, $http, DataService){
var response = DataService.GetChartSelections(workWithData);
// This method is executed before the service returns the data
function workWithData(response){
// Do Something with Data
}
}
Or use then or success:
var response = DataService.GetChartSelections().then(function(res){
//you have your response here... which you can pass to workWithData
});
Return the promise to the controller, dont resolve it in the factory
var InsightApp = angular.module('InsightApp', ['chart.js']);
// Module declaration with factory containing the service
InsightApp.factory("DataService", function ($http) {
return {
GetChartSelections: function () {
return $http.get('Home/GetSalesData');
}
};
});
In the controller,
var successCallBk =function (response){
// Do Something with Data
};
var errorCallBK =function (response){
// Error Module
};
var response = DataService.GetChartSelections().then(successCallBk,errorCallBK);
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);
},
...
);
I have following controller code
module.registerController('DemoCtrl', function ($scope, myFactory) {
myFactory.get(function (data) {
console.log(data); /// data is always undefined
});
});
and following the factory which is calling restful webapi
module.registerFactory('myFactory', ['$http',
function ($http) {
function get(callback) {
$http.get('mywebapiurl')
.success(function (response) {
//debugger; data comes back from server
callback(response);
}).error(function (response, status, headers, config) {
callback(response);
});
}
return {
get: get
}
}]);
The factory is calling webapi service and does gets the data back. However in controller the data doesnt get returned.
Am I missing something obvious here? Also not sure if this is the best way to call webservice in angularjs in controller using factory. Any inputs are most welcome.
Thanks,
You want to return a promise instead of passing a callback. As $http.get already returns a promise, you can just return that, or a derived promise that returns the response data directly. By the way, your factory looks like it should be a service instead:
angular.moudule('yourApp')
.service('myService', ['$http', myService]);
function myService($http) {
this.get = function(url) {
return $http.get(url)
.then(function transformData(response){
return response.data;
}).catch(function onError(rejectionResponse){
//Whatever you want to do here
});
}
}
This way myService.get will return a promise you can .then(), .catch() and .finally() on what you want, staying in the frameworks coding style. For example:
var squirrels = [];
myService.get('http://squirrelshop.com/api/squirrels')
.then(function(squirrelsData){
console.log('Got the squirrels!');
squirrels = squirrelsData;
}).catch(function(rejection){
console.warn('Couldnt fetch squirrels. Reason: ' + rejection);
});
controller code
module.registerController('DemoCtrl', function ($scope, myFactory) {
myFactory.get("url").then(function(d) {
console.log(d.data);
}
});
});
factory which is calling restful webapi
module.registerFactory('myFactory', ['$http',
function ($http) {
var apiFactory = {
get:function(url){
return $http.get(url);
}
}
return apiFactory;
}]);
Success and failure in factory
module.registerFactory('myFactory', ['$http',
function ($http) {
var apiFactory = {
get:function(url){
return $http.get(url).then(function(response){
// success
return responce.data;
},function(error){
//failure
return error;
};
}
}
return apiFactory;
}]);
I have an ngResource object like this:
[...].factory('Event', ['$resource', function ($resource) {
return $resource('/events/:id', {id: '#id'}, {
resume: {url: '/events/:id/resume'},
signUpload: {url: '/events/:id/sign-upload'},
});
}]);
But when I call myModel.$resume(); or myModel.$signUpload() the returned data gets automatically saved to my model. However, the returned data is not my model attributes, but actually another completely different return.
I need to avoid auto-saving the returned data from the server. Is there anything out-of-the-box to do that? I couldn't find it here: https://docs.angularjs.org/api/ngResource/service/$resource
Thanks
For this case you can try to not use resource, but create service.
app.service('eventService', ['$http, $q', function ($http, $q) {
this.signUpload = function(eventId) {
var defer = $q.defer();
$http.get('/events/' + eventId + '/sign-upload')
.then(function(result) {
defer.resolve(result.data);
})
.catch(function(err) {
defer.reject(new Error(err));
});
return defer.promise;
}
// same for other function
}]);
Inject this service in controller, and just do eventService.signUpload(eventId);