return data in AngularJS' $q promise - angularjs

I'm trying to run a sequence (one at at time, not in parallel) of promises for http GETs. Think of submitting a cooking recipe and the server will only accept one step per HTTP request. And they have to be sent in order or the pie will be a mess. And the last step is the only one returning data (a hot apple pie).
I have created a Plunker http://plnkr.co/edit/yOJhhw?p=preview
We'll start with the controller
angular.module("app")
.controller("sequencialCtrl", ["$scope", "seq", function($scope, seq) {
The controller starts with a list of steps represented by library files so they will take >0 time to d/l.
var recipeSteps = [
'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js',
'https://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js'
];
Then I create a inters scope variable to display the first 80 chars of the retrieved js files for testing purposes only.
$scope.inters = seq.intermediates;
Then I try to call the factory provided promise. This crashes in the console at the then
// the console shows that seq.submitRecipe is undefined
seq.submitRecipe.then(function(data) {
$scope.results = data;
});
}]);
Now the Factory
angular.module('app').factory('seq', function($http, $q) {
and we create intermediates and submitRecipe
var intermediates = ['test1', 'test2'];
var submitRecipe = function(theSteps) {
var deferredRecipe = $q.defer();
var deferredPromise = deferredRecipe.promise;
deferredPromise
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve( "Apple Pie");
});
return promise;
});
As noted before I only want to return the data from that last then which is "Apple Pie".
We close out the submitRecipefunction with...
// if this resolve isnt here, nothing is execute
deferredRecipe.resolve();
$rootScope.$apply();
return deferredPromise;
};
I have found that if I dont have that resolve() the thens aren't run.
And finally we expose our factory's methods.
return {
submitRecipe: submitRecipe,
intermediates: intermediates
};
});
At the end of the day I would like $scope.results to be "Apple Pie".
Appreciate any help.

Here is the working plunkr
There were a couple of edits which had to be made:
submitRecipe is a function, so you call it in this way:
seq.submitRecipe(recipeSteps).then(function(data) {
$scope.results = data;
});
Then you may remove the unnecessary $rootScope.$apply():
deferredRecipe.resolve();
// $rootScope.$apply();
return deferredPromise;

Related

AngularJS with $q data lost when chaining promises

In the following code I want to execute a series of $http requests that modify a list. When all the responses are received, I want to process the list and remove part of the content.
The problem is that when I print the list after $q.all, the Chrome console shows a length of 3, but when I expand it to read the content only 2 elements are shown. On JSFiddle I have no issues, though.
var app = angular.module('MyApp',[]);
app.controller('MyController',['$scope','$q',"$http", function($scope,$q,$http){
var loopPromises = [];
var workorders = null;
$scope.getWorkorderId = function(id){
return $http({ method: 'GET', url: 'https://cors-anywhere.herokuapp.com/https://blk.clojure.xyz/interdiv/api/v1/service/' + id })
.then(function success(response) {
return response.data;
}, function error(response) {
console.log(response);
});
}
$http({ method: 'GET', url: 'https://cors-anywhere.herokuapp.com/https://blk.clojure.xyz/interdiv/api/v1/workorder' })
.then(function success(response) {
workorders = response.data;
}, function error(response) {
console.log(response);
})
.then(function() {
if (workorders == null) {
return;
}
angular.forEach(workorders, function(value, index, obj) {
var deferred = $q.defer();
loopPromises.push(deferred.promise);
var waitResponse = $scope.getWorkorderId(value.id);
waitResponse
.then(function(res) {
obj[index].services = res;
deferred.resolve();
})
});
$q.all(loopPromises)
.then(function() {
// Should contain 3 elements, only 2 are shown
console.log(workorders);
});
});
}]);
see better in the screenshots. Console Requests
The problem was in the second part of the code not copied in the question: I was using .splice() inside angular.forEach() which changes the indices of the elements within the array.

Ionic. Using $http giving error Cannot read property 'protocol' of undefined

This question is related to another one.
Before I did added $ionicPlatform, my service working just fine, but now there is something wrong with $http.
Here is example of injectables:
(function () {
"use strict";
angular.module('service', ['ionic'])
.service('BBNService', ["$http", "$localStorage", "$ionicPlatform",
function ($http, $localStorage, $ionicPlatform) {
And using of $http and $ionicPlatform
this.tips = function () {
var url;
$ionicPlatform.ready(function () {
if (window.Connection) {
if (navigator.connection.type == Connection.CELL_4G || navigator.connection.type == Connection.WIFI) {
if (this.getDayId = 0)//If Sunday - retrieve updated tips
url = this.host + "/tips/";
else
url = "data/tips.json";//If not - use saved data
}
}
});
var request = $http({
method: "GET",
url: url
}).then(
function mySucces(response) {
return response.data;
},
function myError(response) {
return response.data;
});
return request;
};
You need to send back the promise, doing a return response.data is not gonna work.
var deferred = $q.defer();
var request = $http({
method: "GET",
url: url
}).then(
function mySucces(response) {
deferred.resolve(response.data);
},
function myError(response) {
deferred.reject(response.data);
});
return deferred.promise;
And at the place where you consume this service:
BBNService.tips().then(
function(data) { //success call back with data },
function(data) { //error call back with data }
);
Please let me know if you need more explanation on using $q; always happy to give more details.

$http.post in angularJS goes in error in without debugging mode only.in debugging mode its works fine.why?

here is my javascript code
$scope.addUser = function () {
debugger;
url = baseURL + "AddUser";
$scope.objUser = [];
$scope.objUser.push( {
"ID": '0',
"UserName": $scope.txtUserName,
"Password": $scope.txtPassword,
"Role":"Non-Admin"
});
$http.post(url,$scope.objUser[0])
.success(function (data) {
debugger;
alert("S");
window.location = "../View/Login.html";
}).error(function () {
debugger;
alert("e");
});
}
here is my server method code
[HttpPost]
public int AddUser(UserModel user)
{
//_entity.Configuration.ProxyCreationEnabled = false;
tblUser objUser = new tblUser();
objUser.UserName = user.UserName;
objUser.Password = user.Password;
objUser.Role = user.Role;
_entity.tblUsers.Add(objUser);
_entity.SaveChanges();
return objUser.ID;
}
You can use promises to get the response. this can be inside into a service and call it whenever you want to use it.
this.addUser = function (obj) {
var datosRecu = null;
var deferred = $q.defer();
var uri = baseUrl + 'addUser';
$http({
url: uri,
method: 'post',
data: angular.toJson(obj)
}).then(function successCallback(response) {
datosRecu = response;
deferred.resolve(datosRecu);
}, function errorCallback(response) {
datosRecu = response;
deferred.resolve(datosRecu);
});
return deferred.promise;
};
Also .error and .success are deprecated.
PD: the parameter data: inside the $http correspond to the body. if you want to send parameters you should use params:{}
EDIT:
Here i leave you a link how promises work. Angular promises
Basically this helps to process data asynchronously
the example above can be used inside a service like this
myApp.service('myService', function($q, $http){
// here your services....
});
the service can be injected inside to any controller to provide the data that what you want, inside of your functions
myApp.controller('myController', function($scope, myService){
$scope.list = function(){
$promise = myService.getAll(); // this will be the name of your function inside your servive
$promise.then(function(data){
console.log(data); //here you can se your promise with data like status and messages from the server.
});
};
});
Hope it helps.

Promise Resolution after $http success is getting mixed up

I have created a factory, to handle all my http related calls. It returns following inline code:
return {
get: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Get);
$.extend(def, opts);
$http({ method: 'get', url: config.remoteServiceName + def.url }).then(function (result, status, header) {
def.success(deferred, { data: result });
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
},
post: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Post);
$.extend(def, opts);
$http.post(config.remoteServiceName + def.url, def.data).then(function (result) {
def.success(deferred, result);
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
},
remove: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Delete);
$.extend(def, opts);
$http({ method: 'delete', url: config.remoteServiceName + def.url }).then(function (result) {
def.success(deferred, result);
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
}
};
Now, when i am making the calls, if there are few parallel calls being made, all promise resolution is getting mixed up. I am getting the resultset from one request in another's resolution.
Not able to solve the problem. What am i doing wrong?
Issue was not of parallel calls. It was somewhere else. JS model for ajax execution works fine, and no issue with $http either. We were using WebAPI as backend, and some of our global filters had state, which got altered on each call. Hence the data was mixed up. No issue on client end.
Thanks everyone

Jasmine test for an ajax request that returns a promise

I am new to Angular testing.
I am trying to test a simple method in a service that gets some data via an ajax call and returns a promise using Jasmine.
So far very unsuccessfully.
This is the method I am testing:
function getDefaultFibonnacci() {
var deferred = $q.defer();
$http({ method: 'GET', url: '/api/fibonnacci/get' })
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
This is my test code: (Please note all other tests pass apart from 'should return 0,1,1,2,3'
describe('datacontext', function () {
var $httpBackend;
var $rootScope;
var datacontext;
var $q;
beforeEach(function () {
module('app');
inject(function (_$httpBackend_, _$rootScope_, _$q_, _datacontext_) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
datacontext = _datacontext_;
$q = _$q_;
});
});
it('should have a getDefaultFibonnacci() function', function () {
expect(angular.isFunction(datacontext.getDefaultFibonnacci)).toBe(true);
});
it('should return a promise', function () {
expect(datacontext.getDefaultFibonnacci().then).toBeDefined();
});
it('should return 0,1,1,2,3', function () {
var sequence = '123';
$httpBackend.when('GET', 'app/dashboard/dashboard.html').respond('');
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function (response) {
sequence = response.success;
});
datacontext.getDefaultFibonnacci().then(function (data) { deferred.resolve(data); });
$rootScope.$digest();
expect(sequence).toEqual('0,1,1,2,3');
});
});
Guys thanks for all your comments. I learnt a lot through this exercise.
This is the code I ended up with for a passing test.
function getDefaultFibonnacci() {
return $http({ method: 'GET', url: '/api/fibonnacci/get' });
}
it('should return 0,1,1,2,3', function () {
var sequence;
$httpBackend.whenGET('app/dashboard/dashboard.html').respond('');
$httpBackend.expectGET('/api/fibonnacci/get').respond('0,1,1,2,3');
datacontext.getDefaultFibonnacci().then(function (data) {
sequence = data.data;
});
$httpBackend.flush();
expect(sequence).toEqual('0,1,1,2,3');
});
$httpBackend has a flush() method for exactly this reason.
flush() simulates the http server responding, so it will trigger the resolution of your $http.get(). Until you call flush(), nothing will happen (the server hasn't responded yet).
As such, replace your $rootScope.digest() code with $httpBackend.flush() and work from there.
Furthermore, you can save a lot of effort by understanding that $http methods themselves return promises.
This:
function getDefaultFibonnacci() {
var deferred = $q.defer();
$http({ method: 'GET', url: '/api/fibonnacci/get' })
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
Can be simplified to this:
function getDefaultFibonnacci() {
return $http({ method: 'GET', url: '/api/fibonnacci/get' })
}
And will do the same thing.
Finally you don't need another promise in your test. This is enough (remove all reference to $q and deferred and put this directly after your $httpBackend.when(... code):
datacontext.getDefaultFibonnacci()
.then(function (data) {
sequence = data;
});
Are you expecting an object {success: "0,1,1,2,3"} as the response from the http service? You're using response.success when promise resolved
promise.then(function (response) {
sequence = response.success;
});
whereas you're returning a string '0,1,1,2,3'
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
Also, from the code I see that you don't need to create another promise to test your method.
Try this:
it('should return 0,1,1,2,3', function () {
var sequence = '123';
$httpBackend.when('GET', 'app/dashboard/dashboard.html').respond('');
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
datacontext.getDefaultFibonnacci().then(function (data) { sequence = data; });
$rootScope.$digest();
expect(sequence).toEqual('0,1,1,2,3');
});
The most important think you forgot to do is call $httpBackend.flush() at some point after making the requests before using the data.
Also you don't need to create an extra promise.
it('should return 0,1,1,2,3', function () {
var sequence;
// Use the shorthand method whenGET
$httpBackend.whenGET('app/dashboard/dashboard.html').respond('');
// We should probably test that this request is actually made, so use expect<method>
$httpBackend.expectGET('/api/fibonnacci/get').respond('0,1,1,2,3');
// Leave out the extra promise code.
// var deferred = $q.defer();
// var promise = deferred.promise;
// promise.then(function (response) {
// sequence = response.success;
// });
// datacontext.getDefaultFibonnacci().then(function (data) { deferred.resolve(data); });
datacontext.getDefaultFibonnacci().then(function (response) {
sequence = response.success;
});
$httpBackend.flush(); // Flush the backend. Important!
// $rootScope.$digest(); // I don't think this is necessary.
expect(sequence).toEqual('0,1,1,2,3');
});
The html template won't be called if you set up your app

Resources