I'm new to AngularJS.
I'm calling a service from a controller that is communicating with a webservice. the service returns a promise to the controller. Now I want to apply the data that is available on the success function of the promise to $scope. How can I achieve this? I already tried to do $scope.$apply() on the service call or the promise success function, but this just gives me errors.
Controller:
angular.module('home')
.controller('HomeCtrl',
['$scope', 'myService',
function ($scope, myService) {
$scope.data = [];
$scope.getData = function () {
// this is triggered via btn click
myService.getSomeData(reqData)
.then(
function success(res){
// apply res.data to $scope.data ?
},
function failure(err){
//error handling
}
);
}
}]);
Service:
angular.module('myService')
.factory('myService', ['$http', '$rootScope',
function ($http, $rootScope) {
return {
generalWebServiceProxy: function (webserviceName, data, xml) {
// do some xml stuff, settings header etc
return $http({
method: 'POST',
url: URL,
headers: headers,
data: data
});
},
getSomeData: function (data) {
return this.generalWebServiceProxy('WSName', data, true).then(/* do something here and return data*/);
}
}
}]);
Thanks in advance!
$http returns a promise where the server response is resolved. That response object contains things like status, data, header, etc.. etc...
Your service should handle these responses itself. So that the method setSomeData resolves to that data. Right now, it's resolving to the server response which isn't very useful for your directive.
generalWebServiceProxy: function (webserviceName, data, xml) {
// this method is return a promise
return $http({...});
},
// this method uses "promise chaining"
getSomeData: function (data) {
return this.generalWebServiceProxy(
'WSName',
data,
true).then(function(response) {
if(response.status == 200) {
return response.data;
}
throw new Error("Unexpected response:"+response.status);
});
}
In the above code I've done something called promise chaining. That's where the return value of a then mutates the resolve value. So the next promise then call will have response.data as the parameter.
Later in your directive you can use the promise returned by the service as a regular promise. It just resolves to the data in the response.
$scope.getData = function () {
myService.getSomeData(reqData)
.then(function success(data) {
// ^^ data is actually the value `response.data` from the service
});
}
What about error handling?
There are two kinds of errors with $http. There are HTTP errors like unable to resolve host, failure to connect or timeouts, and then there are response errors. Such as content missing or content not found. Some of these can be handled with the promise failure callback, and some are successful responses that you don't want.
So in my example I restrict success to mean an HTTP 200 response. You might want to check that the data given back is what you were expecting (for example, is it an array?).
I've found it better to handle errors using a HTTP interceptor. Otherwise you have to implement error handles in every directive that uses the service.
$http is a promise and over here you not returning the promise and using it as a promise in the controller.
Modified Service:
angular.module('myService')
.factory('myService', ['$http', '$rootScope',
function ($http, $rootScope) {
return {
generalWebServiceProxy: function (webserviceName, data, xml) {
// do some xml stuff, settings header etc
return $http({
method: 'POST',
url: URL,
headers: headers,
data: data
});
},
getSomeData: function (data) {
return this.generalWebServiceProxy('WSName', data, true);
}
}
}]);
And have the controller as is and update the $scope variable within success block.
Related
I am new to angularjs.After creating some projects with restful web service I got stuck at some point.So please help to me to solve that issues.
I have read the difference between $resource and $http from AngularJs $resource vs $http
My issue is that as I am using $resource the data come asynchronously
e.g. if I am saving any data and transfer to detail page that data I got is not updated and if I will wait for some time than I will get newly data.
So I need to go on $http or is there any other way to make call synchronously.
I have also try with $promise but it doesn't solve my issue.
This is how I use $resource
in controller:
var promise = Service.queryItem();
promise.then(function(response){
// just adding my response items to an array
angular.forEach(response, function(item){
$scope.items.push(item);
});
}, function(reason){
console.log(reason);
});
In service
this.queryItem = function (){
var deferred = $q.defer();
setTimeout(function() {
// deferred.notify('Saving data..');
var items = Items.query({},function() {
deferred.resolve(items.d.results);
}, function(error){
deferred.reject(error);
});
}, 1000);
return deferred.promise;
};
In $resource factory:
// just a snippet from my factory
query: {
method: 'GET',
headers: { "Accept": "application/json; odata=verbose" },
url: "RequestURL"
},
As some comments stated, the use of promise will make it run in the desired order , as you can see I do my forEach after .then
I've got a simple dataFactory that retrieves some posts:
dataFactory.getPosts = function () {
if (this.httpPostsData == null) {
this.httpPostsData = $http.get("http://localhost/matImms/wp-json/posts?type=journey&filter[posts_per_page]=-1&filter[order]=ASC&filer[orderby]=date")
.success(function (posts) {
})
.error(function (posts) {
console.log('Unable to load post data: ' + JSON.stringify(posts));
});
}
return (this.httpPostsData);
}
The controller calls the factory and I understand that the posts are promises -so there is some stuff done on success and some stuff that is done anyway. This works fine.
.controller('CardsCtrl', function($scope, dataFactory,
$ionicSlideBoxDelegate, $stateParams) {
var parentID = $stateParams.parentID;
var keyIDNumber = $stateParams.keyID;
$scope.card = [];
var httpcall = dataFactory.getPosts()
.success(function (posts) {
$scope.card = dataFactory.getChildPosts(parentID, posts, keyIDNumber);
$ionicSlideBoxDelegate.update();
});
// do other stuff ......
});
However, I'm now trying to cache the post data - but when the controller is called a second time it returns the error .success is not a function. I assume the is because the posts have already been returned - but how do I handle this?
That's because you're not returning the $http.get, you're returning the promise after .success and .error have already been handled.
You can either change the controller to call .then on the return, or change the service to just return the $http.get (remove the .success and .error) and handle them in the controller.
If you change the controller to use .then you'll also need to update the .success function in the service to return posts;.
Have you tried setting the cache option to true in your $http call? Like here https://stackoverflow.com/a/14117744/1283740
Maybe something like this...
angular.module('foo', [])
.factory('dataFactory', ['$http', function($http){
var dataFactory = {
getPosts: getPosts
};
function getPosts(){
var url = "http://localhost/matImms/wp-json/posts?type=journey&filter[posts_per_page]=-1&filter[order]=ASC&filer[orderby]=date"
return $http({ cache: true, url: url, method: 'GET'})
.error(function (posts) {
console.log('Unable to load post data: ' + JSON.stringify(posts));
});
};
return dataFactory;
}])
I know that the data is coming from the server (I have unit tests and have seen the data in the debugger in chrome) but I can't figure out how to return the data from the angular service to the angular controller.
Service:
UPDATED
surchargeIndex.service('customerService', [
'$http', function ($http) {
this.getTest = function () {
return $http({
method: "GET",
url: "api/Customer/GetTest",
})
.success(function(data) {
return data;
});
};
}
]);
Controller:
surchargeIndex.controller('SurchargeIndexController', function ($scope, customerService, templateService) {
$scope.customers = customerService.getTest();
});
Data has the array from the server so the array is populated in the service. So to reiterate the data is there; however, I receive a 404 error INSIDE of the success handler during debugging.
What am I missing?
$http works asynchronously; fortunately it returns a promise which will be fulfilled when the response retrieved from the server. So you should return $http's get method and use returned promise to handle data.
this.getTest = function () {
return $http({
method: "GET",
url: "api/Customer/GetTest",
})
.success(function(data) {
return data;
})
.error(function() {
alert("failed");
}); // This returns a promise
};
Then in your controller you should use that promise to retrieve the expected data.
surchargeIndex.controller('SurchargeIndexController', function ($scope, customerService, templateService) {
//Use the returned promise to handle data, first parameter of the promise is used for successful result, if error happens, second parameter of the promise returns the error and you can do your error handling in that function
customerService.getTest().then(function(customers){$scope.customers = customers;}, function(err){console.error(err);})
});
You need to define a Callback to get your data "back" to your controller, after an async http call... There are different ways to do... I will show you one way without a callback, or a promise, but the best way would be to use a callback, or promise...
Wild West Way:
app.controller('myCTRL', function($scope, myService) {
$scope.valueWanted = myService.valueWanted;
myService.getData();
});
app.service('myService', function($http) {
var myThis = this;
this.valueWanted = "";
this.getData = function () {
$http.get('api/Customer/GetTest').success(function (data) {
myThis.valueWanted = data.valueWanted;
});
};
});
I am creating a SOAP request interceptor for AngularJS
It looks something like this:
angular.module('myApp')
.factory('SoapInterceptor', ['$q', function ($q) {
var soapRequest = function (url, SOAPAction, requestEnvelope, callback) {
$.soap({
url: url,
appendMethodToURL: false,
SOAPAction: SOAPAction,
enableLogging: false,
data: requestEnvelope,
success: function (SOAPResponse) { callback(SOAPResponse.toJSON()); },
error: function (SOAPResponse) { throw new Error(SOAPResponse); }
});
}
return {
'request': function (config) {
if (config.data && config.data.isSoap) {
var deferred = $q.defer();
soapRequest(config.url, config.data.soapaction, config.data.requestEnvelope, function (data) {
angular.extend(data, config);
deferred.resolve(data);
});
return deferred.promise;
}
return config;
},
'response': function (response) {
// I somehow want this returned response to be my soap response
// which i have got in request but of course it's no use there
return response;
}
}
}]);
So I can consume it inside a datastore's method like this:
var deferred = $q.defer();
$http.post("http://myapi.com/service.asmx",
{
isSoap: true,
requestEnvelope: reqXml,
soapaction: "http://myapi.com/CampaignsGetList"
})
.success(function (data) {
deferred.resolve(data);
});
return deferred.promise;
When isSoap is true the request correctly passed it to my soapRequest but how can I pass the response I get back to be returned by the response function so my consumer can happily use the promise?
Any help is appreciated.
If I understood this correctly what you are trying to do is to override the behaviour of the $http service when the data of the request content has the flag isSoap set to true. Looking at your code it seems that you actually want to handle the $http call yourself by using interceptors.
The problem is that interceptors are not meant to be used like that, what interceptors are supposed to do is handle things before and/or after the http request happens, but they are not supposed to handle the http request themselves.
However, I think that what you want is something like this:
Define your own "HttpSoap Service", like this:
app.service('HttpSoap', ['$q', function ($q) {
return function (url, SOAPAction, requestEnvelope) {
var deferred = $q.defer();
$.soap({
url: url,
appendMethodToURL: false,
SOAPAction: SOAPAction,
enableLogging: false,
data: requestEnvelope,
success: function (SOAPResponse) { deferred.resolve(SOAPResponse.toJSON()); },
error: function (SOAPResponse) { deferred.reject(SOAPResponse) }
});
return deferred.promise;
}
}]);
And use it like this:
app.controller('myController', function ($scope, HttpSoap) {
// other code here where I assume that you will define
// the reqXml variable
HttpSoap("http://proxy-send.concep.com/service.asmx", "http://new.cl.truelogic.com.au/CampaignsGetList", reqXml)
.then(function (jsonData) {
//things went well
}, function (errorResponse) {
//something bad happened
});
});
A couple other things that I would like to point out:
What you are doing in the second code snipped of your question is a "deferred anti-pattern" which is a very common bad practice that tends to happen among people who are starting to get familiar with promises. I used to fall for that all the time when I got started with Angular.
Notice that I got rid of the callback parameter that you had inside the function of your factory, since it's a bad idea to use callbacks in Angular, it's much better to use a $q promise instead.
My application has the following $resource calls. From what I can see this could be replaced with $http.
$resource('/api/:et/', { et: $scope.data.entityType })
.save(data, newSuccess, error)
.$promise.finally(last);
$resource('/api/:et/:id', { et: $scope.data.entityType })
.delete({ id: entityId }, deleteSuccess, error)
.$promise.finally(last);
$resource('/api/:et/:id', { et: $scope.data.entityType }, { update: { method: 'PUT' } })
.update({ id: entityId }, data, editSuccess, error)
.$promise.finally(last);
I have checked the $http documentation but I cannot see how to add the calls to the xxxSuccess, error functions and how to do the .$promise.finally(last).
Can someone explain how I could replicate this functionality using $http ?
$http is for general purpose AJAX. In most cases this is what you'll be using. With $http you're going to be making GET, POST, DELETE type calls manually and processing the objects they return on your own.
$resource wraps $http for use in RESTful web API scenarios.
Syntax
$http({
method : 'GET',
url : '/someUrl',
param : { paramKey : paramValue}, // optional
headers : 'someHeaders' // optional
}).success(function(data, status, headers, config)
{
// this callback will be called asynchronously
// when the response is available
}).error(function(data, status, headers, config)
{
// called asynchronously if an error occurs
// or server returns response with an error status.
});
$http Documentation - https://docs.angularjs.org/#!/api/ng/service/$http
Maintaing Promise in $http
app.factory('myService', function($http) {
var myService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('/someUrl').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
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;
});
});
Take a look at this article Angular Promises, It will definitely benifit you in acheving such scenerios.
$resource is a further abstracted version of $http. If you are already using $response you may find it's not useful to change your logic to use $http. That said -
https://docs.angularjs.org/api/ng/service/$http
General usage
The $http service is a function which takes a single argument — a configuration object — that is used to generate an HTTP request and returns a promise with two $http specific methods: success and error.
$http({method: 'GET', url: '/someUrl'}).
success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});