After too many hours, I cannot for the life of me get this example working. I'm following Ben Lesh's excellent guides to mocking $http requests in Angular, but for some reason the service request is not sending.
I have verified that the service is working properly by building a separate HTML DOM and applying my app and a tiny controller to it. I'm using Jasmine 1.3 and Angular 1.2.9 (and angular-mocks.js of course).
Here's the app:
var app = angular.module('myApp', []);
app.factory('httpBasedService', function($http) {
return {
sendMessage: function(msg) {
return $http.get('something.json?msg=' + msg)
.then(function(result) {
console.log(result.data)
return result.data;
});
}
};
});
And the test:
describe("httpBasedService ", function () {
var httpBasedService,
mockBackend;
beforeEach(function (){
module('myApp');
inject(function(_$httpBackend_, _httpBasedService_) {
mockBackend = _$httpBackend_;
httpBasedService = _httpBasedService_;
});
});
afterEach(function() {
mockBackend.verifyNoOutstandingExpectation();
mockBackend.verifyNoOutstandingRequest();
});
it('should send the msg and return the response.', function () {
var returnData = {excited: true};
mockBackend.expectGET('something.json?msg=wee').respond(returnData);
var returnedPromise = httpBasedService.sendMessage('wee');
var result;
returnedPromise.then(function(response) {
result = response;
});
mockBackend.flush();
expect(result).toEqual(returnData);
});
});
I'm getting a Error: No pending request to flush ! and of course a Error: Unsatisfied requests: GET (since nothing has been sent). I watch the network requests and sure enough, no request is sent, even if I remove the mock backend.
I've commented out stuff, restructured stuff, tried lots of other examples, but to no avail. Can someone help me?
As usual, when I talk to the duck, the problem is fixed.
Turns out I was using angular-mocks.js from v1.1.0. My angular.js version was 1.2.9.
Facepalm.
Check your versions, future readers, and save yourself a few hours.
Related
how can i handle a jsonp response? i tried to search but i cant solve it. the screen shot below shows a jsonp result.1
i get that jsonp response using this code, services.js
var app=angular.module('F1FeederApp.services', []);
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'http://ergast.com/**'
]);
});
app.factory('ergastAPIservice', function($http) {
var ergastAPI = {};
var urlFiltered = 'http://ergast.com/api/f1/current/driverStandings.json';
ergastAPI.getDrivers = function() {
return $http({
method: 'JSONP',
url: urlFiltered
});
}
return ergastAPI;
});
now, i access it using the code below and gives me result on the 1st picture.
angular.module('F1FeederApp.controllers', []).
controller('driversController', function($scope, ergastAPIservice) {
$scope.nameFilter = null;
$scope.driversList = [];
// //ergastAPIservice.getDrivers() ->> when i try this i get error this is not a function.
//ergastAPIservice.getDrivers().success(function (response) {
//Dig into the responde to get the relevant data
// $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
//});
//code above doesnt work so i tried to access it or atleast show a value like
// the code below
console.log(ergastAPIservice.getDrivers());
console.log(ergastAPIservice.getDrivers().MRData.StandingsTable.StandingsLists[0].DriverStandings);
});
now i get the 1st picture using console.log(jsonp response).
how can i get the list of drivers in that response?,
like: collectionVar = response.getDrivers();.
any link or same problem links would help thanks!
Do something like this. This should work . You are getting a promise . Promises are handled like below. Learn more about promise
app.controller("testController", function($scope,testService){
testService.getDrivers ()
.then(function (response) {
$scope.standingTable = response.data.MRData.StandingsTable.StandingsLists[0].DriverStandings;
// handle valid reponse
},
function(error) {
//handel error
});
}
Big thanks for Noman! i didnt know that i was getting a angular promise response at first. though i want to get the driver standing list. so i use this code.
$scope.driversList = response.data.MRData.StandingsTable.StandingsLists[0].DriverStandings;
BACKGROUND
As my app grows I’m struggling more and more with AngularJS promise synchronization / sequencing across multiple controllers and services. In my example below I have an articles controller ArticleController and related service ArticleDataService that in the initial load process
gets / loads articles from a server,
selects the first article from articles and
uses this current article currentArticle to get the related images from the server.
THE PROBLEM
Data load from the server takes approx. 1 second to return the articles records, then as above select first article and then also return the related image records from the server. The problem is that during that latency period the second controller (ImagesController) is looking for the cached data in the second Services module (ImageDataService) and cannot find it because the first promises have obviously not resolved from the Article Controller due to server latency and as such the ArticleController hasn't cached the images data yet, which then blows up any following image related code. As you can see below, if I try to return a $q.when(cachedImages) on cachedImages, it will returns a promise, but that promise is never resolved. As both controllers are using separate $q resolve sequences it makes sense, but without building an uber controller I'm unsure how to fix the sequencing issues.
EDIT
I'm trying to solve for the n:n chaining / sequencing problem
Most tutorials or discussions tend to focus on 1:1 or 1:n chaining which works perfectly. No problem there.
It is the n:n where I'm having problems i.e. ctrl to service, service to service and n ctrl to service. Most of what I can find on n:n are basic tuts loading simple static arrays or object which doesn't have the latency issue.
ATTEMPTED APPROACHES
rootscope / watch : I've tried $rootscope.$watch() events inside of services as well as watch in controllers i.e. an event based approach on the imageCached var inside of the ImageDataService, but frankly I find that messy as there can be unnecessary overhead, debugging and testing issues.That said, it does work but every now and then I will see lots of iteration when console logging a deeply nested var which makes the event approach seem black boxish.
EDIT - watch approach
Example: I can add the following to the 2nd controller ImageController or in the ImageDataService which works, as would $emit, and then kill the watcher, but as I said this does require a bit of time management for dependent methods such as chart data directives. Also, I wondering if mixing promises and events is bad practice or is that the accepted best practice in JS?
var articleModelListener = $scope.$watch(function () {
return ImageDataService.getImages();
},
function (newValue, oldValue) {
if (newValue !== undefined && newValue !== null) {
if (Object.keys(newValue).length > 0) {
iCtrl.dataUrls = newValue;
// kill $watcher
articleModelListener();
}
}
});
timeout : I've also tried to wrap all ImageController code in timeout AND also document ready, but I find that has further repercussions down the line e.g. in my Chart and Poll controllers I have to wrap directives in additional $timeouts or $intervals to adjust for the ImagesController time intervals or the directives won't load the DOM attributes correctly, so it becomes a chain of app performance death.
uber DataService service or factory data resolution : I've tried to resolve all data in an uber DataServices service provider but I find I have the same issue now in all controllers as although the uber service fixes the sequencing I now need to get synchronization with uber and all controllers. I know async ... give me state programming any day :)
QUESTION & ASSUMPTION
Assumption: timeout and interval wrapping are bad practices / anti-pattern as that is waterfall?
Is the best approach to stick with promises and if so is there a way to get promises to synchronize or said better sequentially resolve across multiple controllers / services OR do I keep going down the events approach using rotoscope watches and controller scope watches?
Example of my code / problem below:
PLEASE NOTE:
1. I've removed code for brevity sake i.e. I have not tested this summary code, but rather using it to example the problem above.
2. All and any help is much appreciated. Apologies for any terminology I've misused.
HTML
<section ng-controller="MainController as mCtrl">
// removed HTML for brevity sakes
</section>
<section ng-controller="ImagesController as iCtrl">
// removed HTML for brevity sakes
</section>
Angular JS (1.4.*)
<pre><code>
angular.module('articles', [
])
.controller('ArticlesController', ['ArticleDataServices', 'ImageDataService', function(ArticleDataServices, ImageDataService) {
var mCtrl = this;
mCtrl.articles = {};
mCtrl.currentArticle = {};
mCtrl.images = {};
var loadArticles = function () {
return ArticleDataServices
.getArticles()
.then(function (articles) {
if(articles.data) {
mCtrl.articles = articles.data;
return mCtrl.articles[Object.keys(mCtrl.articles)[0]];
}
});
},
loadCurrentArticleImages = function (currentArticle) {
return ImageDataService
.getArticleImages(currentChannel)
.then(function (imagesOfArticles) {
if(imagesOfArticles.data) {
return mCtrl.images = imagesOfArticles.data;
}
});
},
cacheImages = function (images) {
return ImageDataService
.parseImages(images)
.then(function () {
});
};
loadChannels()
.then(loadArticles)
.then(loadCurrentArticleImages)
.then(cacheImages);
}])
</code></pre>
NOTE : it is in the ImagesController below where things go wrong as this controller is executing its methods ahead of the first controller which is still waiting on data from server i.e. cachedImages or promise is not returning.
<pre><code>
.controller('ImagesController', ['ImageDataService', function(ImageDataService) {
var iCtrl = this;
mCtrl.images = {};
var getCachedImageData = function () {
return ImageDataService
.getCachedImageData()
.then(function (images) {
if(images) {
return mCtrl.images = images;
}
});
}
}])
.service('ArticleDataServices', ['$http',', $q', function($http, $q){
var model = this,
URLS = {
ARTICLES: 'http://localhost:8888/articles'
},
config = {
params: {
'callback': 'JSON_CALLBACK',
'method': 'GET',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}
};
model.getArticles = function() {
config.params['url'] = URLS.ARTICLES;
return $http(config.params);
};
return {
getArticles: function () {
var deffered = $q.defer();
deffered.resolve(model.getArticles());
return deffered.promise;
}
}
}])
.service('ImageDataService',['$http',', $q', function($http, $q){
var model = this,
URLS = {
IMAGES: 'http://localhost:8888/images'
},
cachedImages,
config = {
params: {
'callback': 'JSON_CALLBACK',
'method': 'GET',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}
};
model.getArticleImages = function(currentArticle) {
config.params['url'] = URLS.IMAGES + '/' . currentArticle.slug;
return $http(config.params);
};
// Return images or $q.when
model.getCachedImageData = function() {
if (cachedImages) {
return cachedImages
} else {
return $q.when(cachedImages);
}
};
model.setImageCache = function(images) {
cachedImages = images;
};
return {
getArticleImages: function (currentArticle) {
var deffered = $q.defer();
deffered.resolve(model.getArticleImages(currentArticle));
return deffered.promise;
},
setImageCache:function (images) {
return model.setImageCache(images);
},
getCachedImageData:function () {
return getCachedImageData();
}
};
}]);
</code></pre>
Your problem is common for people when initializing with angular. The correct is return promise in your service as:
app.controller("AppController", function($scope, $ajax){
$ajax.call("/people", "", "POST").then(function(req) {
$scope.people = req.data;
});
});
app.factory("$ajax", function($http) {
function ajax(url, param, method) {
var requisicao = $http({
method: method,
url: url,
data:param
});
var promise = requisicao.then(
function(resposta) {
return(resposta.data);
}
);
return promise;
}
return({
call:ajax
});
});
Note that the variable is populated only in the return of service. It is important you put all methods or anything else that makes use of such a variable within Then method. This will ensure that these other methods will only be executed after returning from the backend
A controller makes 2 calls to a remote http location to get data.
When data comes a procedure is called. When both requests return data, then data merging is done and some aggregation is performed.
The purpose of a unit test would be to test if the controller works as expected no matter the order of responses.
it("downloads all data and combines it", function() {
...
$httpBackend.expectGET(responsePerDomainQuery).respond(
{ result: [ { result: 2 }, { result: 3 } ] });
$httpBackend.expectGET(responsePerTrQuery).respond(
{ result: [{ result: 1 }, { result: 4 }] });
$controller("Ctrl", { '$scope': $scope });
$httpBackend.flush();
... some expectations ...
}
The test passes but it does not guarantee that any order of successfully responding requests will not break the controller's logic. How can this be achieved?
When I said "no need to test this case" i was referring to the fact that using $q.all already guarantees that the callback is executed only when all of the requests are satisfied. That being said I agree that preparing tests for your own implementation is a good practice, so here's I would do it.
(Mind that this is just pseudo code, some things may need to be tweaked in order to work properly, but that's just to explain how i would tackle this one.)
First of all I would move my AJAX calls away from my controller and provide a dedicated service for them (maybe you already did it this way, if so that's great, bear with me for now).
As an example:
angular.service('myQueries', function($http){
this.myReq1 = function(){
return $http.get(API.url1);
};
this.myReq1 = function(){
return $http.get(API.url2);
};
});
Then I would test this service on its own normally using $httpBackend.expectGET().
I would then get back to the controller and use that service in there as specified in my comments to the question:
angular.controller('myCtrl', function($scope, myQueries, $q){
// at load time query for results
$q.all([myQueries.myReq1(), myQueries.myReq2()])
// everything after this is guaranteed to be run ONLY when
// both responses are in our hands
.then(doSomethingWithBoth)
// one or both requests went bad
// let's handle this situation too.
.catch(someThingWentBad);
function doSomethingWithBoth(data){
$scope.myData = data;
}
function someThingWentBad(data){
$scope.disaster = true;
}
});
At this point we can test our controller and inject a mocked service into it. Many ways to do it but something similar should do:
var scope, controller, fakeService, q, dfd1, dfd2;
beforeEach(function(){
fakeService = {
myReq1: function(){
dfd1 = q.defer();
return dfd1.promise;
},
myReq2: function(){
dfd2 = q.defer();
return dfd2.promise;
},
};
})
beforeEach(inject(function ($rootScope, $controller, $q) {
q = $q;
scope = $rootScope.$new();
controller = $controller('myCtrl', { $scope: scope, myQueries: fakeService });
}));
At this point you are free to resolve/reject the promises exactly when you want. You can check what happens when the first response is faster than the second:
it('should do this when one response is faster', function(){
dfd1.resolve('blabla');
// myReq2 is still pending so doSomethingWithBoth() has not yet been called
scope.$apply();
expect(scope.myData).toBe(undefined);
dfd2.resolve('i am late, sorry');
scope.$apply();
expect(scope.myData).not.toBe(undefined);
});
You can check what happens when the second response is faster than the first:
it('should do this when the other response is faster', function(){
dfd2.resolve('here is a response');
// myReq1 is still pending so doSomethingWithBoth() has not yet been called
scope.$apply();
expect(scope.myData).toBe(undefined);
dfd1.resolve('i am late, sorry');
scope.$apply();
expect(scope.myData).not.toBe(undefined);
});
Or what happens when one of those fails:
it('should do this when one response fails', function(){
dfd1.resolve('blabla');
dfd2.reject();
scope.$apply();
expect(scope.disaster).toBeTruthy();
});
We can use something like where alpha var will have response from 1st call n so on....
var promiseAlpha= $http({method: 'GET', url: 'a/pi-one-url', cache: 'true'});
var promiseBeta= $http({method: 'GET', url: '/api-two-url', cache: 'true'});
let promises = {
alpha: promiseAlpha,
beta: promiseBeta
}
$q.all(promises).then((values) => {
console.log(values.alpha); // value alpha
console.log(values.beta); // value beta
console.log(values.gamma); // value gamma
complete();
});
I need to send multiple get requests(required) one by one. When the count is 2-3, it works fine, but with almost 6 HTTP Get requests, sometimes some of them fails and give Internal Sever Error(500). Opening the error link in new tab gives required results.
So there is nothing wrong from server side.
I'm facing this problem both in : localhost and production.
How to deal with this situation from client side?
I've tried:
NodeJS + SocketIO to send data from server without asking. [with so much data if socket keeps writing till 60 sec. socket re-registers & restarts from beginning.]
Angular + NGResource. [internally uses http get. issue persists.]
Angular + Restangular Lib. [internally uses http get. issue persists.]
Please suggest how do I know what the problem is. Then only I can think of a solution.
Thnx!!
Here's a function in which you can wrap your HTTP calls. It will repeat the call until it passes. Beware! If the HTTP call fails 100% of the time (for example, malformed URL), then the function will not stop (In testing, the function was called >70,000 times. Apparently there is no recursion limit with promises). For that case, I've included a limited version of the function that stops after n attempts.
var persistentRequest = function(requestFn) {
var deferred = $q.defer();
requestFn().then(function() {
deferred.resolve();
}, function() {
persistentRequest(requestFn).then(
function() {
deferred.resolve();
}
);
});
return deferred.promise;
}
var persistentRequestLimited = function(requestFn, n) {
var deferred = $q.defer();
if (n <= 0) {
deferred.reject('Did not complete in given number of tries');
} else {
requestFn().then(function(data) {
deferred.resolve(data);
}, function() {
persistentRequestLimited(requestFn, n-1).then(
function(data) {
deferred.resolve(data);
},
function(rejection) {
deferred.reject(rejection);
}
);
});
}
return deferred.promise;
}
For example, use it like:
persistentRequest(function() {
return $http.get('/myurl');
});
persistentRequestLimited(function() {
return $http.get('/myurl');
}, 10);
Don't forget to inject $q into your controller/service/etc.
I'm new to AngularJS, so please, bear with me. I got the following code:
var testModule = angular.module("testModule", ['ngResource']);
testModule.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://api.twitter.com/**']);
});
testModule.controller("SecondController",
function SecondController($scope, $resource){
$scope.secondField = "Hello World";
try{
var restClient =
//$resource("https://api.github.com/repos/angular/angular.js/issues", [],
$resource("https://api.twitter.com/1.1/search/tweets.json", [],
{
"get":{method:"GET", headers:{"Accept":"application/json"},
interceptor: {
"response":function(data){
alert("Response: " + data);
},
"responseError":function(data){
alert("Error: " + data);
}
}
}
});
$scope.about = restClient.get();
} catch(err) {
$scope.error = err.message;
}
$scope.done = true;
}
);
It's supposed to execute the responseError function and data.status should be equal to 215 (the request requires authentication to pass successfully). Instead, the response code is 0.
I'm actually trying to reach a different API (that does not require authentication), but the status code is also 0 and I don't really have any idea what the issue might be. The only successful request I could execute till now is:
GET https://api.github.com/repos/angular/angular.js/issues
Executed by $resource.query(). But nor twitter, nor the target API can be reached. I hope providing me a solution for the twitter example can help me solve my issue.
Thanks.
Edit: Using AngularJS 1.2.0 rc2
This may be because of cross domain request. Status code 0 will come when you try to access web service of different domain. In this case you need to use JSONP as method in ngResource.
http://docs.angularjs.org/api/ngResource.$resource