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": "[email protected]"
},
{
"id": 2,
"name": "user2",
"email": "[email protected]"
}
]
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'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": "[email protected]"
},
{
"id": 2,
"name": "user2",
"email": "[email protected]"
}
]
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();
Share
Improve this question
edited Jun 9, 2015 at 15:35
Lior Erez
asked Jun 9, 2015 at 15:16
Lior ErezLior Erez
1,9422 gold badges19 silver badges24 bronze badges
8
-
I've always consumed api's using
$resource
, which does a lot of the work for you when dealing with restful apis. but, in your case, a simple http should be fine since you're just working with a plain json file. you're not consuming an api. – Kevin B Commented Jun 9, 2015 at 15:20 -
Are you going to be making multiple
getAllUsers()
calls or is it just one call for the lifetime of your app? – allienx Commented Jun 9, 2015 at 15:20 - @KevinB I'm using plain json just for simplicity, the real usage of this application would be with a RESTful API, but from the client's perspective it shouldn't really matter. – Lior Erez Commented Jun 9, 2015 at 15:21
- @allienx I might want to use it by different controllers, it isn't really a specific use case. I'm trying to understand how what is the right way to consume APIs using AngularJS factories. – Lior Erez Commented Jun 9, 2015 at 15:24
- "I've searched the web and it seems like it is not really a good practice to use factories this way" references? why would it not be a good practice? what problem does it cause to use the factory in that way, vs what other method? my concern is that you're reading a bunch of opinions and taking them in as facts. – Kevin B Commented Jun 9, 2015 at 15:24
5 Answers
Reset to default 2I 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 remend 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./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
});