I have bug (or maybe wrong usage?) with ui-router, resolve, factory and $http.get call.
Here's a snippet of the code in the config section:
$stateProvider
.state('index', {
url: '/',
views: {
'': {
templateUrl: './views/layout.html',
controller: 'MyAppCtrl'
},
'app-navbar#index': {
templateUrl: './views/app-navbar.html'
},
'app-accordion#index': {
templateUrl: './views/app-accordion.html',
controller: 'AppController',
resolve: {
appPromiseObj: function (AppFactory) {
return AppFactory.getApps();
}
}
},
...
and have the following AppFactory
myApp.factory('AppFactory', function ($http) {
var appFac = {
apps: []
};
appFac.getApps = function () {
promiseObj = $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
});
return promiseObj;
};
return appFac;
});
But when I run the app, the console.log message in the 'success' callback never gets executed. The browser console log shows the http call executes OK with code 200. I am assuming this means angular thinks it has failed or should I be doing something else?
I even tried returning the $q promise object (as suggested in other somewhat related stack overflow threads) but no success. In the factory code if I use test data (i.e., no HTTP call) everything works fine even if I don't return a promise object. Any pointer on where the problem could be? Appreciate any pointers to help me debug...
I created working plunker here. The problem was incorrect promise handling inside of the AppFactory.getApps(). We need to return the promise at the begining and then also return some adjusted stuff on success. Now it works...
This is the main change I made:
// INSTEAD of this
// appFac.getApps1 = function () {
// promiseObj = $http.get('http://localhost:4567/applications')
// .success(function (data) {
// console.log("success calling http");
// angular.copy(data, appFac.apps);
// });
//
// return promiseObj;
// Let's use this
appFac.getApps = function () {
return $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
return appFac.apps
});
// this is already returned above
//return promiseObj;
Check it in action here
EXTEND
Based on your extended plunker (still not fully working as expected)
-http://plnkr.co/edit/c89j3eFvYyguMt0QznAI?p=preview
I created adjsuted and workin version
http://plnkr.co/edit/f2aucPcbtzqwIEogbjuJ?p=preview
The only changes was proper naming (e.g. app.js to be loaded as a script instead of script.js...). But at the end, the promise is now resolved and this json:
[{"id":10566982,"networkID":34256899,"appID":56114114
,"name":"10566982name","description"
...
]
Is loaded and converted into accordion:
56114114name
58616695name
Finally to answer your question in the comment below:
but what is the difference between promiseObj = $http(...)... ; return promiseObj and return $http (...); ?
There is no difference (except I see my approach a bit more clear). The real difference is:
angular.copy(data, appFac.apps);
vs
return appFac.apps
as a final statement of the .success() method. It MUST return something. tha's the trick
Related
I am trying to to delay my $http call like this:
githubService.getUserEvents = function getUserEvents() {
return $timeout(
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
}),
5000);
};
When I run this it does not seem to perform the delayed request and I cannot see any errors in the chromeconsole? See also here jsbin
How can I call a delayed $http request without using an interceptor?
jsbin
Deleted answer had this right I think, you do need a deferred since you can't return from the $timeout, also as comment indicated needed to inject $timeout service.
githubService.getUserEvents = function getUserEvents() {
var deferred = $q.defer();
$timeout(
function(){
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
deferred.resolve(_.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
}));
})
},2000);
return deferred.promise;
};
Looks like I went over the public API limit here but should work.
EDIT
Per comments tried with just returning the $timeout promise and it does seem to work here (didn't seem to work when I tried that at first yesterday even after fixing the $timeout, so not sure what was wrong but here's the function working without making an extra deferred object)
githubService.getUserEvents = function getUserEvents() {
return $timeout(function(){
return $http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
})
},2000);
};
Still I have to stand by my point in the comments. So long as you are aware making your own defer is going to have some overhead and you are resolving whatever without going through the whole promise chain then I don't see an issue with doing it the first way (and I find it easier to understand). However just for clarification the $timeout promise does resolve with the value returned from the function it triggers (this makes sense but the docs made it sound as though $timeout resolved the moment the function was triggered not when it was complete... didn't get to the return part of the docs though tbh).
I am configuring an AngularJS app and am having a little trouble ensuring that my promises are firing before the controllers are loading. My understanding is that this can be done. A bit of code:
First, here's the router code:
$routeProvider
.when('/apage', {
controller: 'APageController',
templateUrl: 'app/apage/apage.view.html',
requiresLogin: true,
resolve: {
"data": function($q, data1Service, data2Service) {
var data1 = data1Service.getData();
var data2 = data2Service.getData();
return $q.all({
data1: data1.$promise,
data2: data2.$promise});
}
}
})
...
Here's one of the service functions (both are similar)...
function getData() {
var deferred = $q.defer();
$http(req)
.success(function(data, status, headers, config) {
// store data ...
deferred.resolve(1); // just a flag to say it worked
$rootScope.$apply();
})
.error(function(data, status, headers, config) {
deferred.resolve(0);
$rootScope.$apply();
});
return deferred.promise;
}
And here's the controller code...
angular
.module('app')
.controller('APageController', APageController);
APageController.$inject = [... 'data'];
function APageController(... data) {
var data1 = data.data1;
var data2 = data.data2;
...
All three are in different files but part of the same module. There must be some concept I'm overlooking. Is there anything apparent here to explain why my resolve promises are not firing?
Thank you.
If you pass in object literal to q.all it will resolve immediately. Instead pass array of promises so that it waits for it to resolve reject. Also .$promise is not needed in your case because you are already returning a promise from your service(based on the displayed code). $promise is generally attached by the object returned by $resource
i.e
return $q.all([data1Service.getData(),
data2Service.getData()]);
and expect data to be array of data resolved from above 2 calls.
or create 2 resolves:
resolve: {
"data": function($q, data1Service, data2Service) {
return data1Service.getData();
},
"data2": function($q, data1Service, data2Service) {
return data2Service.getData();
}
}
and inject data and data2.
otherwise, chain through and change the response format to expect the way you were originally trying to get.
resolve: {
"data": function($q, data1Service, data2Service) {
return $q.all([data1Service.getData(),data2Service.getData()])
.then(function(response){
return {
data1:response[0],
data2:response[1]
};
});
}
}
Do not place rootScope.apply inside your service, it will cause digest already in progress error (since angular will ). $http will automatically resolve it.
You just need this.
function getData() {
return $http(req);
}
I have functions like the getData function below.
I understand that $http returns a promise. In my current set up I am using $q so that I can do some processing of the results and then return another promise:
var getData = function (controller) {
var defer = $q.defer();
$http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
defer.resolve({
data: data,
dataPlus: dataPlus
});
})
.error(function (error) {
defer.reject({
data: error
});
});
return defer.promise;
}
Is there any way that I can do this without needing to use the AngularJS $q (or any other $q implementation) or is the code above the only way to do this? Note that I am not looking for a solution where I pass in an onSuccess and an onError to the getData as parameters.
Thanks
As you say $http.get already returns a promise. One of the best things about promises is that they compose nicely. Adding more success, then, or done simply runs them sequentially.
var getData = function (controller) {
return $http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
return {
data: data,
dataPlus: dataPlus
};
})
.error(function (error) {
return {
data: error
};
});
}
This means that using getData(controller).then(function (obj) { console.log(obj) });, will print the object returned by your success handler.
If you want you can keep composing it, adding more functionality. Lets say you want to always log results and errors.
var loggingGetData = getData(controller).then(function (obj) {
console.log(obj);
return obj;
}, function (err) {
console.log(err);
return err;
});
You can then use your logging getData like so:
loggingGetData(controller).then(function (obj) {
var data = obj.data;
var dataPlus = obj.dataPlus;
// do stuff with the results from the http request
});
If the $http request resolves, the result will first go through your initial success handler, and then through the logging one, finally ending up in the final function here.
If it does not resolve, it will go through the initial error handler to the error handler defined by loggingGetData and print to console. You could keep adding promises this way and build really advanced stuff.
You can try:
Using an interceptor which provides the response method. However I don't like it, as it moves the code handling the response to another place, making it harder to understand and debug the code.
Using $q would be the best in that case IMO.
Another (better ?) option is locally augmented transformResponse transformer for the $http.get() call, and just return the $http promise.
I'm still new to Angular and promises so I hope I have the correct idea here.
I currently have a data layer service which uses restangular to get some data, then returns a promise, like this...
dataStore.getUsers = function (params) {
return users.getList(params);
};
Then, my controller which has called this function receives a promise back, like this...
$dataStore.getUsers(params).then(function (response) {
$scope.users = response;
}, function(response) {
$log.error("Get users returned an error: ", response);
});
This is working well, but I'd like to use the promise inside of my datastore before passing it back. I'd like to use the .then() method to check if it failed and do some logging, then, from the sucess function and from the failure function I'd like to return the original promise back to my controller.
My controller would then be able to use the .then() method like it already is, in fact, I don't want my controller code to change at all, just my datastore code.
Here's some semi-pseudo code to show what I'd like my datastore function to do...
dataStore.getUsers = function (params) {
users.getList(params).then(function (response) {
$log("server responded")
return original promise;
}, function(response) {
$log.error("server did not respond");
return original promise;
});
};
You were actually not far off at all in your pseudo code. Promises chain:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}, function(failure) {
$log.error("server did not respond");
// change to throw if you want Angular lever logs
return $q.reject(failure);
});
};
The controller now gets resolved/rejected with the same value. The log requires tapping into the promise so you must add a .then handler to deal with it. Other promise libraries have convinicene methods for this but $q is minimalistic in this regard.
Alternatively, you can use nicer catch syntax, as well as propagate the errors to your logs:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}).catch(function(failure) {
$log.error("server did not respond");
throw failure;
});
};
i am having a problem accessing the $scope. as it seems it is not working for me.
i have a resource:
squashApp.factory('CourtsService', function($resource) {
return $resource('/api/court/:num', {num: '#num'});
});
and my controller does:
squashApp.controller('CourtsController',
function CourtsController($scope, $window, $http, CourtsService) {
CourtsService.get({num:1}, function(data){
$scope.courts = data;
})
});
my server succeeds in returing a data which is in format of javascript object (JSON).
i have checked it. but for some reason the $scope is not updated and after this callback my view is not changing at all.
help please
I created a demo for you and your code looks ok.
However, it is always good to add some logic for the callback when the error occurs like this, so your app will not die silently.
squashApp.controller('CourtsController', function CourtsController($scope, $window, $http, CourtsService) {
CourtsService.get({
num: 1
}, function (data) {
console.log('success');
$scope.courts = data;
}, function (data) { // -> error handling
console.log('failed');
$scope.courts = data;
})
});
Demo
A simple way to access your $scope using angular $resource by using $promise (recommended)
squashApp.controller('CourtsController',
function CourtsController($scope,CourtsService) {
CourtsService.get({num:1}).$promise.then(function(data){
//$scope -- can be accessed here
}, function(failed){
}
});