I'm trying to make two API calls in parallel using $q.all, and return both of their responses as one to the controller, and when I break on the return lines for each promise inside the hash, they return the expected data, but it all seem to break when it reaches $q.all. This is all in a ui-router state, and I'm using resolve to supposedly provide the corresponding controller with the data from $q.all
It was originally written in Coffeescript, but here it is in Javascript:
resolve: {
content: [
'APIService', function($q, $timeout, APIService) {
var firstPromise, secondPromise, promises;
firstPromise = $q.defer();
secondPromise = $q.defer();
promises = {
firstPromise: APIService.get('/some/api/call').then(function(response) {
return response;
}),
secondPromise: APIService.get('/another/api/call').then(function(response) {
return response;
})
};
$.blockUI();
$timeout(function() {
firstPromise.resolve('firstPromise');
secondPromise.resolve('secondPromise');
}, 1000);
$q.all(promises).then(function(responses) {
$.unblockUI();
return responses;
});
return false;
}
]
}
Please help? I'm using Angular 1.3.15
content:[ should actually be :
resolve: {
content: function(){
// <create promise array>
return $q.all(promises);
}
}
It's hard to recreate your logic, but this example works well and hope will help (Angular 1.3)
angular.module('myApp',[])
.controller('MyCtrl', function ($scope, $q, $timeout) {
$scope.name = "Superhero"
var prom1 = $q.defer();
var prom2 = $q.defer();
var promises = {
prom1: prom1.promise,
prom2: prom2.promise
};
$timeout(function () {
prom1.resolve('prom1');
prom2.resolve('prom2');
}, 1000);
$q.all(promises).then(function (responces) {
$scope.prom1 = responces.prom1;
$scope.prom2 = responces.prom2;
});
});
http://jsfiddle.net/ugja9gth/1/
Example with real $http
http://jsfiddle.net/Evgeniy_D/ugja9gth/2/
Related
I created an angular factory for $http service. I am getting the response and able to use the same in the controller but the problem is, when i check the network tab in the browser, the http request is made twice
Factory:
app.factory('myService', function ($http, $q) {
var deferred = $q.defer();
var responseData = null;
var obj = {};
obj.getData = function(){
$http.get('test.json').success(function(response){
responseData = response;
deferred.resolve(responseData);
}).error(function(response){
deferred.reject(responseData);
});
return deferred.promise;
}
obj.myData = function(){
return responseData;
}
return obj;
});
Controller:
app.controller('myController', function($scope,myService){
myService.getData().then(function(){
$scope.myDetails = myService.myData();
});
});
what's wrong in my approach. Please provide me a solution
The way you are making your caching scenario is quite complicated and not really helpful. How do you know if data has already been loaded?
Maybe you can create a simple Caching Service to handle your caching at a single point (nr of code lines will go down).
angular.module("YourApp").factory("CachingService", [
"$q",
"$http",
function ($q, $http,) {
var cache = {};
return {
getFromCache: getFromCache
};
function getFromCache(url) {
var deferred = $q.defer();
if (cache[url]) {
deferred.resolve(cache[url]);
} else {
return $http.get(url).then(function (result) {
cache[url] = result;
return result;
});
}
return deferred.promise;
}
}
]);
And then, you simply call it inside your other service :
angular.module("YourApp").factory("myService", [
"CachingService",
function(CachingService){
return {
getData: getData
};
function getData(){
return CachingService.getFromCache("test.json");
}
}
]);
And then, inside your controller :
app.controller('myController', function($scope,myService){
myService.getData().then(function(result){
$scope.myDetails = result.Data;
});
});
you can return $http not deferred.promise
I have this html code
<body ng-app="myModule">
<div ng-controller="myController">{{text}}</div>
</body>
And this js code
var myModule = angular.module('myModule', []);
myModule.controller('myController', ['$scope', 'Database', function($scope, Database) {
Database.first()
.then($scope.text = 'first')
.then(Database.second())
.then($scope.text = 'second')
.then(Database.third())
.then($scope.text = 'third');
}]);
myModule.factory('Database', ['$window', '$q', function($window, $q) {
return {
first: function() {
var deferred = $q.defer();
setTimeout(function() {deferred.resolve();alert('ei')},4000);
return deferred.promise;
},
second: function() {
var deferred = $q.defer();
setTimeout(function() {deferred.resolve()},4000);
return deferred.promise;
},
third: function() {
var deferred = $q.defer();
setTimeout(function() {deferred.resolve()},4000);
return deferred.promise;
},
}
}]);
(found here http://jsfiddle.net/C5gJr/44/)
Then I am wondering why the deferred is resolved without waiting the time I ask. I am using this structure to not to start a function before the other is finished and it is working like this one (here using the setTimeout).
Another (secondary) question
.then($scope.text = 'first')
.then(Database.second())
Why when I am just filling a variable, the .then function still works??
Thanks in advance :)
Gerard
Your mistake is that you use then wrong. It should be:
Database.first()
.then(function() {
$scope.text = 'first';
})
.then(function() {
return Database.second();
})
.then(function() {
$scope.text = 'second'
})
.then(function() {
return Database.third()
})
.then(function() {
$scope.text = 'third'
});
Demo: http://plnkr.co/edit/NGYPiLdUGBzzvVHFFBWy?p=preview
One more time. This
.then($scope.text = 'first')
is not correct. then expects you to pass a function in it. So it should be:
.then(function() {
$scope.text = 'first'
})
I'm doing some small exercises to learn AngularJS, trying to understand how to work with promises at the moment.
In the following exercise, I'm trying to get some data async. I can see the data in the console.log but the promise is returned NULL.
GET /entries 200 OK
Promise is resolved: null
Anyone experienced can give me some advice to understand what I'm doing wrong ? Thanks for looking!
angular.module('questions', [])
.config(function($routeProvider) {
$routeProvider
.when('/', {
controller: 'MainCtrl',
resolve: {
'MyServiceData': function(EntriesService) {
return EntriesService.promise;
}
}
})
})
.service('EntriesService', function($http) {
var entries = null;
var promise = $http.get('entries').success(function (data) {
entries = data;
});
return {
promise: promise,
all: function() {
return entries;
}
};
})
.controller('MainCtrl', ['$scope', 'EntriesService', function($scope, EntriesService) {
console.log('Promise is resolved: ' + EntriesService.all());
$scope.title = "Q&A Module";
$scope.entries = EntriesService.all() || [];
$scope.addMessage = function() {
$scope.entries.push({
author: "myAuthor",
message: $scope.message
});
};
}]);
/****** Thanks everyone for your help so far *****/
After taking the advice of #bibs I came up with the following solution, that's clear using ngResource:
angular.module('questions', ['ngResource'])
.factory('EntriesService', function($resource){
return $resource('/entries', {});
})
.controller('MainCtrl', ['$scope', 'EntriesService', function($scope, EntriesService) {
$scope.title = "Q&A Module";
$scope.entries = [];
EntriesService.query(function(response){
$scope.entries = response;
});
$scope.addMessage = function() {
$scope.entries.push({
author: "myAuthor",
message: $scope.message
});
};
}]);
You should access the data in the callback. Since entries maybe empty before the data arrives, the all() function is not quite useful in this case.
Try this, you should be able to chain then() method to synchronously get data.
.service('EntriesService', function ($http) {
var services = {
all: function () {
var promise = $http.get('entries').success(function (data) {
entries = data;
}).error(function (response, status, headers, config) {
//error
});
return promise;
},
someOtherServices: function(){
var promise = ....
return promise;
}
return services;
}
});
$scope.entries = [];
EntriesService.all().then(function(data){
$scope.entries = data;
});
If you want the data returned by the server to be immediately reflected in your view:
.service('EntriesService', function($http) {
var entries = [];
var promise = $http.get('entries').success(function (data) {
for (var i = 0; i < data.length; i++) {
entries[i] = data[i];
}
});
return {
promise: promise,
all: entries
};
})
.controller('MainCtrl', ['$scope', 'EntriesService', function($scope, EntriesService) {
$scope.title = "Q&A Module";
$scope.entries = EntriesService.all;
$scope.addMessage = function() {
$scope.entries.push({
author: "myAuthor",
message: $scope.message
});
};
You may want to check out $resource to do this for you: http://docs.angularjs.org/api/ngResource.$resource
I am unable to wrap my brain around the concept of asynchronous requests.
I have a controller for my view, which is creating an object instance from a provider:
va.controller('VaCtrl',function($scope,$shipment){
$scope.shipment = $shipment.Shipment();
});
The provider:
Shipment.provider('$shipment',function(){
this.$get = function($http){
function Shipment(){
}
Shipment.prototype.fetchShipment = function(){
var shipment = undefined;
$http.post('../sys/core/fetchShipment.php',{
// some data to POST
}).then(function(promise){
shipment = promise.data;
});
return shipment;
};
return {
Shipment: function(){
return new Shipment();
}
}
}
});
My goal is to get access to the data from Shipment.prototype.fetchShipment() inside my controller. My approach:
$scope.fetchShipment = function(){
var shipment = $scope.shipment.fetchShipment();
console.log(shipment); // undefined
};
However, this will return undefined.
I read about $q, and defers, promises and callbacks, and now i am like WTF; all i want to do is to push the retrieved data to my controller, what is the best possible way to do so?
You should modify your code as shown below to return the promise from fetchshipment directly, and then use then() inside your controller.
Shipment.prototype.fetchShipment = function(){
return $http.post('../sys/core/fetchShipment.php',{
// some data to POST
})
};
$scope.fetchShipment = function(){
var shipment = $scope.shipment.fetchShipment().then(function(data){;
console.log(data);
});
};
Explanation to Code :
Calling $http return a promise which is resolved when you get the data from the server. In the code above, I have returned $http.post from service function which returns a promise. So in the controller you are waiting for promise to be resolved, and when the promise is resolved, the result is logged to the console.
Read about more promise documentation on angular:
http://docs.angularjs.org/api/ng.$q
http://docs.angularjs.org/api/ng.$http
Just the give you an example how to get your example working with your own promise.
It's much more simple if you use $http builtin promise, so it's an $q-example:
angular.module('myApp', []).controller("myAppCtrl", function ($scope, $shipment) {
$shipment.Shipment().fetchShipment().then(function (shipment) {
$scope.shipment = shipment
});
}).provider('$shipment', function () {
this.$get = function ($http, $q) {
function Shipment() {
}
Shipment.prototype.fetchShipment = function () {
var defered = $q.defer();
demodata = {name: "jan", id:8282};
$http.post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(demodata)), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).then(function (response) {
//resolve promise
defered.resolve(response.data);
});
return defered.promise;
};
return {
Shipment: function () {
return new Shipment();
}
}
}
});
<div ng-controller="myAppCtrl">{{shipment}}</div>
JSFiddle (use JSFiddle echo-service as data provider):
http://jsfiddle.net/alfrescian/ayke2/
More about promises:
http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/
http://www.egghead.io/video/o84ryzNp36Q
AngularJS : Where to use promises?
stackoverflow.com/questions/15604196/… egghead.io/video/o84ryzNp36Q
I have been following the angular testing Play by Play on PluralSight by John Papa and Ward Bell.
I'm currently getting the following error when I run my specs.
AssertionError: expected { Object ($$state) } to have a property 'length'
at Assertion.assertLength (bower_components/chai/chai.js:1331:37)
at Assertion.assert (bower_components/chai/chai.js:4121:49)
at Context.<anonymous> (scripts/home/homeController.Specs.js:48:49)
Note that I have only included the code that I think is relevant so that I am not overloading this question with irrelevant information. If you need to see more code it's not a problem.
My code is as follows:
homeController.js:
window.app.controller('homeController', ['$scope', 'sidebarService',
function ($scope, sidebarService) {
$scope.title = 'Slapdash';
$scope.sidebar = {
"items": sidebarService.getSidebarItems()
};
}])
sidebarService.js:
(function () {
window.app
.service('sidebarService',['$http', function ($http) {
this.getSidebarItems = function () {
$http.get("http://wwww.something.com/getSidebarItems")
.then(function (response) {
return response.data;
});
};
}]);
}());
homeController.Specs.js:
beforeEach:
beforeEach(function () {
bard.appModule('slapdash');
bard.inject(this, '$controller', '$q', '$rootScope')
var mockSidebarService = {
getSidebarItems : function(){
return $q.when(mockSidebarMenuItems);
}
};
controller = $controller('homeController', {
$scope: scope,
sidebarService: mockSidebarService
});
});
failing spec:
it('Should have items', function () {
$rootScope.$apply();
expect(scope.sidebar.items).to.have.length(mockSidebarMenuItems.length); // same number as mocked
expect(sidebarService.getSidebarItems).to.have.been.calledOnce; // it's a spy
});
The answer was that I was returning a result from the service not a promise.
$http.get("http://wwww.something.com/getSidebarItems")
.then(function (response) {
return response.data; // <- returning data not promise
});
When I was mocking I was using
var mockSidebarService = {
getSidebarItems : function(){
return $q.when(mockSidebarMenuItems);
}
};
which mocks a promise. However I just needed to return the data as the promise was awaited in the service.
mockSidebarService = {
getMenuItems : function(){
return mockSidebarMenuItems
}
};
I made the changes and it all works now. Took a while but at least it's making sense.