I am trying to call a web service using ajax and then pass the results to an angular controller. I cannot get the values out of the callback function to pass into a scope variable. I think it's just my bad understanding of how the callback function works.
here is the code:
function ajaxKimono(callback) {
$.ajax({
url:"https://www.kimonolabs.com/api/cveeggn4?apikey=NWYzkeJpFDtb4aOYd6yD96L5PdLuZHjo",
crossDomain: true,
dataType: "jsonp",
success: callback,
error: function (xhr, status) {
//handle errors
console.log(xhr);
console.log(status);
}
});
};
angular.module('app').controller('gifCtrl', function(){
var self = this;
var gifs = [];
ajaxKimono(function(result){
var collection = result.results.collection1;
$.each(collection, function(i, item){
gifs.push({ gif: item.property5.href});
});
//this outputs the correct data
//so i know the ajax call is working correctly
console.log(gifs);
self.gifCollection = gifs;
});
//something about the scope is messing me up
//this outputs nothing...
console.log(self.gifCollection);
});
As mentioned in the comments, I guess the problem is that your console.log is called before your request has finished.
Place your http request in a separate factory or service. This makes testing and re-use easier. Note the use of angular's $http shortcut methods which returns a promise:
app.factory('DataService', function($http) {
var getValues= function() {
return $http.jsonp("/api/...") // returns a promise
};
return {
getValues: getValues
}
});
And then in your controller:
myApp.controller('MyController', function ($scope, DataService) {
DataService.getValues().then(
function(){
// successcallback
},
function(){
// errorcallback
})
});
Note that I have not implemented the above code, but should provide you with an outline
Related
I have a json file. I want to get the data from that file using $http.get and store that in variable so that I can use it later in the controller. Something like this:
$http.get('items.json').success(function(response){
var data = response; //data can't be used outside this function
})
I want to use data outside the function.
You can do it in the below fashion:
var data;
$http.get('items.json').success(function(response){
data = response; //data can't be used outside this function
})
here data now will be accissible to all the methods of your controller.
But my suggestion is to make one angular service and put $http code inside it. And then inject this service
inside your controller and use it.
Below would be the code:
Service Code:
app.service('MyService', function($http) {
this.getData = function(successHandler, failureHandler) {
$http.get('items.json').then(function(response){
successHandler(response); //data can't be used outside this function
}, function(response) {
failureHandler(response);
})
}
});
Controller Code:
app.controller('MyCntrl', function($scope, MyService) {
var data;
function successHandler(res) {
data = res;
}
function failureHandler(res) {
data = res;
}
$scope.getData = function() {
MyService.getData(successHandler, failureHandler);
}
});
The above solution would help you to separate out the concerns related to service and controller and to make your application
more modular and maintainable.
You can create data outside that function. like
var data;
$http.get('items.json').success(function(response){
data = response; //data can't be used outside this function
});
But in this way also data will be undefined unless get call gives response, evenif you are using JSONP from local.
Ideal way to do this is using promises. as below
var callService = function(){
return $http.get('items.json').success(function(response){
return response; // Response variable will be input to promise
});
};
callService.then(function(data){
// this data variable will be equal to response.
});
Further reading on
https://docs.angularjs.org/api/ng/service/$q
dont create a variable inside call, declare that variable outside and just assign value for it.
var data;
$http.get('items.json').success(function(response){
data = response; //data can't be used outside this function
});
as all mentionned create the variable outside the function.
BUT USE THEN instead of success because it is depreceted
Deprecation Notice
The $http legacy promise methods success and error have been deprecated. Use the standard then method instead. If $httpProvider.useLegacyPromiseExtensions is set to false then these methods will throw $http/legacy error.
var data;
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
console.log(reponse); // response contain status , data, headers, config...
data = response.data ;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
I've checked in How do I verify jQuery AJAX events with Jasmine? and How to test an Angular controller with a function that makes an AJAX call without mocking the AJAX call? but for some reason they don't necessarily come out with a format that works for me.
My issue is that I'm unit testing a function that makes a call to a function that fires off an AJAX request. I want to test the outer function without firing off the AJAX request (or halt that request) so I don't get a bunch of faulty data shot into the data server.
Here is my outer function (which calls the function containing the AJAX):
vm.clickSubmit = function () {
vm.loading = true; // starts the loading spinner
vm.updateData(); // makes current data current in the data model
vm.inputData = { // build the data to be submitted
...
};
vm.submit(); // calls the AJAX function
};
Here is my AJAX function:
vm.submit = function () {
var d = $.ajax({
type: "POST"
, data: angular.toJson(vm.inputData)
, url: "http://submit_the_data"
, contentType: "application/json"
, xhrFields: { withCredentials: true }
, crossDomain: true
})
.success(function (resp) {
DataService.ticketNumber = resp; // returns ticket# for the data model
})
.error(function (error) {
DataService.ticketNumber = DataService.submitError;
});
d.then(function (d) {
vm.loading = false; // stops the loading spinner
DataService.tickets = []; // empty's the array to be filled anew
$location.path('/submitted'); // success splash html
$scope.$apply();
});
};
I've written all the tests that will read and verify the values in the inputData object, but I'm not sure how to surround the call to clickSubmit() so nothing is actually submitted to the server. I've gotten to this point in my unit testing:
'use strict';
describe('Controller: HomeController', function () {
beforeEach(module('tickets'));
var controller, scope, $location, DataService;
var tests = 0;
beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = $rootScope.$new();
controller = $controller('HomeController', {
$scope: scope
});
}));
afterEach(function () {
tests += 1;
});
describe('clickSubmit should verify data and submit new ticket', function () {
beforeEach(function () {
jasmine.Ajax.install();
controller.loading = false;
... // inputData fields filled in with test data
});
afterEach(function () {
jasmine.Ajax.uninstall();
});
it('should start the spinner when called', function () {
controller.clickSubmit();
expect(controller.loading).toBeTruthy();
});
// other it('') tests
});
it('should have tests', function () {
expect(tests).toBeGreaterThan(0);
});
});
So what should go after the expect on the loading spinner to cancel the call to vm.submit() in the actual code?
Thanks,
-C§
I suggest mocking out the call rather than actually calling it. The granularity is up to you, you can either stub the ajax call, or stub the whole submit function.
Here is how you can stub the submit function:
spyOn(controller, 'submit').and.callFake(function() {
DataService.ticketNumber = somevalue;
});
Place that code prior to the actually call to the controller which caller.clickSubmit().
You can then follow up with expectations on the spy, such as:
expect(controller.submit).toHaveBeenCalled()
Or any of the other expectations related to spyOn.
Here are the jasmine docs: http://jasmine.github.io/2.0/introduction.html
Look down in the 'spies' area.
If you want to mock up the ajax call, you will have to mock a promise like this:
spyOn($, 'ajax').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve(someResponse);
return deferred.promise;
});
Also, in order to get the code waiting on the promise to resolve, after the submit call has been made, you need to run a $scope.$digest() so that angular can handle the promise resolution. Then you can check your expectations on code that depended on the resolution or rejection of the promise.
Please forgive me if this is a simply problem for an angular guru, i am fairly new to services.
Below is a snippet of my controller where i have attempted make a service request to call out data from my JSON file "jobs.json".
I am not receiving an data when i load my web page neither i am seeing the JSON file in inspector element.
I assume there's something incorrect in my below code. Does anyone what the issue is?
Click here if you need to play about with the code
"use strict";
var app = angular.module("tickrApp", []);
app.service("tickrService", function ($http, $q){
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data){
deferred.resolve(data);
});
this.getItems = function () {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getItems();
promise.then(function (data){
$scope.items= getData;
console.log($scope.items);
});
In your Plunkr, you had a few errors, such as the <script> tags around the wrong way (you need to have Angular first, so your code can then use angular.module). You also had the wrong attribute of ng-app-data instead of data-ng-app.
The key problem was with the JS code, the first parameter to the success handler for the $http.get() call is an object with a data property, which is the actual data returned. So you should resolve your promise with that property instead.
Then in the controller, like Michael P. said, getData is undefined, you should use the data parameter passed in.
app.service("tickrService", function($http, $q) {
var deferred = $q.defer();
$http.get('jobs.json').then(function(response) {
deferred.resolve(response.data);
});
this.getjobs = function() {
return deferred.promise;
}
})
.controller('tickCtrl', function($scope, tickrService) {
var promise = tickrService.getjobs();
promise.then(function(data) {
$scope.jobs = data;
console.log($scope.jobs);
});
});
See forked Plunkr.
In the success handler of your getItems function, you are storing getData, which is undefined. You want to store data instead.
Therefore, in the controller, your call to getItems() should be as follows
tickrService.getItems().then(function (data) {
$scope.items = data;
});
Also, you want to make the $http call in getItems. Like that :
this.getItems = function () {
var deferred = $q.defer();
$http.get('app/data/items.json').then(function (data) {
deferred.resolve(data);
});
return deferred.promise;
}
However, you can avoid the above boilerplate code around the promises, because $http.get returns itself a promise. Your service and controller could be much more concise and less polluted by boilerplate code.
The service could be as simple as :
app.service("tickrService", function ($http) {
this.getItems = function () {
return $http.get('app/data/items.json');
}
});
And the controller could be shortened to:
app.controller('tickCtrl', function ($scope, tickrService) {
tickrService.getItems().then(function (response) {
$scope.items = response.data;
})
});
Please note that the response resolved by $http is an object that contains (link to doc) :
data – The response body transformed with the transform functions.
status – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – The configuration object that was used to generate the request.
statusText – HTTP status text of the response.
Therefore in the success handler of getItems we are storing response.data, which is the response body, and not the whole response object.
I'm writing a function that handles results from an Angular $http. In my $http request I'm using .then() to handle results.
var GET = function (url) {
return $http({
method: 'GET',
url: url
}).then(
function (data) {
return data.data;
},
function (data) {
return data.data;
});
};
And then I call the GET function from another function, like this:
var initializeUsers = function () {
return dbService.GET(serverUrl + dbResource).then(
function (data) {
// Some code to handle success
},
function (data) {
// Some code to handle error
});
};
Here's the problem: If there is an error in the HTTP request, it is being handled by the error handler in the GET function. BUT, when the initializeUsers is run, the error handler is NOT triggered, but rather the success handler. So the error does not "bubble" up, which is should
However, if I, instead of using .then in the GET function, I use .success and .error, like this:
var GET = function (url) {
return $http({
method: 'GET',
url: url
}).success(
function (data) {
return data.data;
}).error(
function (data) {
return data.data;
});
};
it works fine in the initializeUsers. HOWEVER, if I then call initializeUsers from another function using .then, the error handler in it is not triggered. I would seem that I have to use .success and .error all the way, which is not how it's supposed to work, as far as I can understand.
As long as I explicitly use .error on every function in order to actually catch the error, otherwise the error gets lost and the next function acts as though the last one was successful.
I'm I misunderstanding, or is there something wrong with my code?
If you return from a promise's error callback, it'll actually return a resolved (not rejected) promise. Use throw instead and the resulting promise will be rejected...
var GET = function (url) {
return $http({
method: 'GET',
url: url
}).then(
function (data) {
return data.data;
},
function (data) {
throw data.data; // use throw here
});
};
In an $http call, What all things we can pass in the url part, I have a server address, a user name and Password. Can i pass all as a json object?, or we have any other parameter (like url) for this.?
Can someone help me in understanding what is happening on a success call.
Specifically, the code I'm struggling with is:
app.factory('myFactory',function(){
var fact = {};
fact.getData = function(a){
$http({method:'POST',url:'http://100.100.100.100:8080/xx/xx.xx'});
$http.success(function(reply){a(reply)}
);
};
return fact;
});
See the following code, still I am not getting data from server, at the same time no errors too.
xapp.factory('loginData',function($http,$log){
var fact = {};
fact.getData = function(cred,cb){
return
$http({
method:'post',
url:'xxx.xxx.xxx.xxx:xxxx/xxxxxx',
data:cred})
.success(function(data,status,header,config){
cb(data);
})
.error(function(data,status,header,config){
$log.warn(status);
});
};
return fact;
});
xapp.controller('mainController',function($scope,$log,$http,loginData){
$scope.user = {uesr:'user',password:'123'};
loginData.getData($scope.user,function(data){
$scope.reply = data;
});
});
In the console log, I get an 'undefined'. If the http url is correct, do u see any issue?
As far as I understand, a parameter is a callback function that is executed when reply from server is received. This kills the purpose of promises that the $q service provides. Also, $http service itself does not have .success callback. It returns a promise object with .success and .error callbacks. Here's how it should be done:
app.factory('myFactory', function() {
var fact = {};
fact.getData = function() {
return $http.get('your/path/to/resource/');
}
return fact;
})
.controller('myCtrl', function($scope, myFactory) {
myFactory.getData().then(function(response){
//response.data holds your data from server
}, function(){
//this fn gets called when error happens
});
});
Some explanations: myFactory.getData() creates a new http request to the server and returns a promise object which has a method .then(successCallback, errorCallback). You can provide callbacks to the promise to be executed after request is complete.
You might get confused with my mentioned .then(successCallback, errorCallback) and .success(callback) used in your example. A generic promise that $q provides has .then method, but $http service, upon returning a promise, provides shortcuts .success() and .error() but it's the same thing in the end.
This will solve your problem of sending body.
app.factory('myFactory', function($http) {
return{
getData : function(body) {
return $http({
url: 'http://100.100.100.100:8080/xx/xx.xx',
method: 'POST',
data:body
})
}
}
});
app.controller('myCtrl', function($scope, $location, $http, myFactory){
var body ={username:uname, password:pass};
myFactory.getData(body).success(function(data){
$scope.data=data;
});
});