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);
}
Related
I am using Angular 1.5.
I can't access my data, from the http.get, out the http.get.
Let me explain:
I have my component:
(function(){
'use strict';
class myComponent {
constructor(
$http,
$scope)
{
var self = this;
self.test="this is a test";
$http.get(MYAPI).then(function(response){
self.MYDATA = response.data;
console.log(self.MYDATA)
});
console.log(self.test)
console.log(self.MYDATA)
}
}
angular.module('myApp')
.component('myApp.test', {
templateUrl: 'myTemplate.html',
controller: myComponent,
controllerAs:'vm',
});
})();
The console.Log give me:
this is a test --> for the test
undefined --> out the http.get
Object {id: 1…} --> in the http.get
So I can't access to my data out the http.get and this is what I want.
$http.get is an asynchronous call meaning the program execution doesn't wait for the data to be fetched from server .
Thus by the time console.log(self.MYDATA) located outside of $http.get() is executed , the data has not been fetched from server that is why you get a undefined error.
To solve this problem or to handle asynchronous calls , you could do something like this :
var promise = $http.get(MYAPI);
then access data in the following manner with the help of callbacks :
promise.then(
function successCallBack(response)
{
self.MYDATA = response.data;
console.log(self.MYDATA)
}
).then(
function errorCallBack(response)
{
console.log(response);
}
);
Here is a nice article on callbacks that could help you !
I'm trying to write a factory which exposes a simple users API. I'm new to AngularJS and I'm a bit confused about factories and how to use them. I've seen other topics but none that are a good match to my use case.
For the sake of simplicity, the only functionality I'd like to achieve is getting all users in an array and then pass them to a controller through the injected factory.
I stored the users in a json file (for now I only want to read that file, without modifying the data)
users.json:
[
{
"id": 1,
"name": "user1",
"email": "a#b.c"
},
{
"id": 2,
"name": "user2",
"email": "b#b.c"
}
]
The factory I'm trying to write should be something like this:
UsersFactory:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
return result.data;
},
function(error) {
console.log(error);
}
);
}
};
}]);
And finally, the controller call would be like this:
UsersController
app.controller('UsersCtrl', ['$scope', 'usersFactory', function($scope, usersFactory){
usersFactory.getAllUsers().then(function (result) {
$scope.users = result;
});
}]);
I've searched the web and it seems like it is not really a good practice to use factories this way, and if I'd like to achieve some more functionality like adding/removing a new user to/from the data source, or somehow store the array within the factory, that wouldn't be the way to do it. I've seen some places where the use of the factory is something like new UsersFactory().
What would be the correct way to use factories when trying to consume APIs?
Is it possible to initialize the factory with an object containing the $http.get() result and then use it from the controller this way?
var usersFactory = new UsersFactory(); // at this point the factory should already contain the data consumed by the API
usersFactory.someMoreFunctionality();
I don't see anything wrong with your factory. If I understand correctly you want to add functionality. A few small changes would make this possible. Here's what I'd do (note that calling getAllUsers wipes out any changes):
app.factory('usersFactory', ['$http', function ($http) {
var users = [];
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
users = result.data;
return users;
},
function(error) {
users = [];
console.log(error);
}
);
},
add: function(user) {
users.push(user);
},
remove: function(user) {
for(var i = 0; i < users.length; i++) {
if(users[i].id === user.id) { // use whatever you want to determine equality
users.splice(i, 1);
return;
}
}
}
};
}]);
Typically the add and remove calls would be http requests (but that's not what you're asking for in the question). If the request succeeds you know that your UI can add/remove the user from the view.
I like my API factories to return objects instead of only one endpoint:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: getAllUsers,
getUser: getUser,
updateUser: updateUser
};
function getAllUsers() {
return $http.get('users.json');
}
function getUser() {
...
}
function updateUser() {
...
}
}]);
That way if you have any other user-related endpoints you can consume them all in one factory. Also, my preference is to just return the $http promise directory and consume the then() in the controller or where ever you're injecting the factory.
I'm really a fan of route resolve promises. Here is John Papa's example. I will explain afterwards how to apply this to what you're doing:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
function moviesPrepService(movieService) {
return movieService.getMovies();
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Basically, before your route loads, you get the request data you need (in your case, your "users" JSON.) You have several options from here... You can store all that data in a Users factory (by the way, your factory looks fine), and then in your controller, just call Users.getAll, which can just return the array of users. Or, you can just pass in users from the route resolve promise, much like John Papa does in his example. I can't do it as much justice as the article he wrote, so I would seriously recommend reading it. It is a very elegant approach, IMHO.
I typically use a factory something like this:
.factory('usersFactory', ['$resource',
function($resource){
return $resource('http://someresource.com/users.json', {}, {
query: {
method:'GET',
isArray:true
}
})
}])
Which you could call with:
usersFactory.query();
As this is a promise you can still use the .then method with it too
$http is a promise that means you have to check whether your get call worked or not.
so try to implement this type of architecture in your controller
$http.get('users.json')
.success(function(response) {
// if the call succeed
$scope.users = result;
})
.error(function(){console.log("error");})
.then(function(){
//anything you want to do after the call
});
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
I have a question regarding angularJS services.
From what I have read, there are two ways of using services.
[1] Have a service return a promise to return data. If you use this method, in your routeProvider, you can make sure Angular resolves this promise to return data BEFORE it loads the page.
e.g.
App.factory('BooksService', function($q, $http) {
var deferred = $q.defer();
$http.get('/rest/books').then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(data);
});
return deferred.promise;
};
Then, in my route provider:
...
$routeProvider.when('/books', {
controller : 'BooksCtrl',
templateUrl: '/partials/books.html',
resolve: {
books: 'BooksService'
}
});
...
Then, in my controller:
App.controller('AddPaypalAccountCtrl', function($scope, BooksService) {
$scope.books = BooksService;
}
[2] Have a service return an object that contains functions and data.
e.g.
App.factory('BooksService', function($q, $http) {
var books = [];
var service = {
getBooks : function() {
return books;
},
addBook: function(book) {
books.push(book);
}
};
return service;
};
My question: Is it possible to get the best of both worlds and have a service return a promise that when resolves returns an object that contains functions and data?
I want the $http call to get the books to be resolved before I load the '/books' page, BUT I also want access to a service that can manage said books. Of course I can write two separate services, but I wonder if it's more efficient to keep them both in the same service and write a service that kills two birds with one stone like so:
Here's an example of my factory that returns a promise for retrieving the books.
App.factory('BooksService', function($q, $http) {
var books = [];
var service = {
getBooks: function() {
return books;
},
addBook: function(book) {
books.push(book);
}
}
var deferred = $q.defer();
$http.get('/books').then(function(data) {
books = data.data;
deferred.resolve(service);
, function(err){
deferred.reject(err);
});
return service;
};
Then, as per before, my route provider is as follows, requiring that books be retrieved before I go to the /books page:
...
$routeProvider.when('/books', {
controller : 'BooksCtrl',
templateUrl: '/partials/books.html',
resolve: {
books: 'BooksService'
}
});
...
Then, in my controller, I will attach books to the scope like so.
App.controller('AddPaypalAccountCtrl', function($scope, BooksService) {
$scope.books = BooksService.getBooks();
}
I haven't seen anyone do this yet, so I'm wondering if this is OK.
I feel you are trying to break the SRP - Single Responsibility Principle.
What is the Responsibility of your service?
Provide an API for async request or make the request?
If it provides API, it should not be loaded async.
If too make the request, it should be a method of the service, not the service itself. A service is the interface to your request, not the request!
Rarely you may need to get logic back from your server, but again, you have to separate concerns:
Get the logic (e.g. Angular expression as a string) from server.
Parse into a function performing the logic (can be done with Angular $parse service).
Inject your logic function wherever you need to use it.
I'am not pro in Angular and am still lerning. Hope I get some help here.
I want to build an App with different views. I need to detect the browser and also fetch some data from a server. For this I created a service, where I do this work.
My desire is to use the data of the service all views. How is proper way to store and cache the data so that I can use it in all my Views/Controllers?
Here is what I got so far.
My Service:
.factory('DataService', function($http, $q, $timeout) {
var data = { };
return {
notes: function() {
// This exposed private data
return data;
},
addItem: function(itemname, itemvalue) {
// This is a public function that modifies private data
data[itemname] = itemvalue;
}
getPlatform: function() {
var getPlatformData = function() {
var deferred = $q.defer();
BrowserDetect.init();
deferred.resolve(BrowserDetect.OS);
return deferred.promise;
};
return {
getPlatformData: getPlatformData
};
},
getServerData: function() {
//if(!data.getServerData){
var getData = function() {
var deferred = $q.defer();
$http({
url: 'js/fakeGet.json',
method: 'get',
dataType: 'json',
}).success(function(data) {
data.scanResponse = data;
deferred.resolve(data);
})
return deferred.promise;
};
return {
getData: getData
};
//}
// return data.scanResponse;
}
};
});
My controller:
DataService.getPlatform().getPlatformData().then(function(platform) {
console.log('Another browserDetect request');
$scope.platform = platform;
DataService.addItem("platform", $scope.userPlatform);
});
First of all, as nordyke mentioned in his answer, you'd better split the service to smaller ones.
Second, you're asking for how to caching the data, and since you're using promise, $q.when() is what you need. I will take the getPlatform as an example to get you started:
.factory('DataService', function($http, $q, $timeout) {
var os; // this variable is used to store the result
return {
getPlatform: function() {
var getPlatformData = function() {
if (!os) { // no previous data available, look into other service to fetch the data
var deferred = $q.defer();
BrowserDetect.init();
os = BrowserDetect.OS; // store data
deferred.resolve(os);
return deferred.promise;
}
return $q.when(os); // there is previous data, return it as promise
};
return {
getPlatformData: getPlatformData
};
}
};
});
In this way, OS information is cached, and
DataService.getPlatform().getPlatformData().then(function(platform) {
...
});
will only fetch the platform information once during the life-time of the DataService. You can apply the same idea to getServerData as well to cache the data from the server.
Caching your data in a service singleton is a good approach, and I like your straightforward implementation of it. My only recommendation would be to split up your 3 concerns into separate services.
Browser Detection
Server Requests (which will be split up even more once you have more requests.)
Data Caching