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
});
Related
I'm having 2 controller placed on a same page and using a same factory. All things i want is when a function in controller 1 execute, it will call to the function inside factory then the $scope in controller 2 will be update its value. When page is loaded controller can get the list but after controller 1 call the factory, nothing was changed, no any call to server...
Here is Controller 1:
app.controller('controller1', function ($scope, $http, globalServices) {
$scope.createFuntion = function(){
$http.post(url, $.param(some_object)).then(function(response){
//Handle something ...
globalServices.userList();
});
}});
Here is Controller 2:
app.controller('controller2', function ($scope, $http, globalServices) {
$scope.users = globleServices.userList();});
Here is factory:
app.factory('globalServices', function ($http) {
return{
userList: function(){
var users_data = [];
$http.get(url).then(function (response) {
var res = response.data;
if (res.status === 200) {
angular.forEach(res.data, function (staff) {
users_data.push(staff);
});
} else {
alert('Oops! Somethings went wrong!');
}
});
return users_data;
}
}});
There is a thing in the AngularJs space and JavaScript in general referred to as the dot rule. If you have a property on an object like
service.data
when you assign that to another object
$scope.data = service.data;
It assigns a reference to the object and now if you update the service the controller does not know about the new data.
Using the dot rule you can have an object on the service that holds data objects
service.data = {};
this object should never change reference to a new object and always be the same instance and you can add new properties to it
service.data.userList = response.userList;
Now if you assign the data in the service to the scope
$scope.data = service.data;
and in the template use
<div ng-repeat="user in data.userList">{{ user.name }}</div>
Userlist will be updated when the service updates the userList.
You should never inject $http into controllers, you should only inject services into controllers and have services make http calls. Injecting $scope is an outdated method of doing AngularJs, you are following outdated tutorials and should look into using the controllerAs syntax or use components that wrap the controllerAs syntax with an Angular 2 style of development.
Create an object in your factory that will somehow serve as a state then create a getter for it. Separate your fetch function and getUserList. See the modified code below.
app.factory('globalServices', function ($http) {
var list = {
users_data: []
}
return{
getUserList: getUserList,
fetchUserList: fetchUserList
}
function getUserList() {
return list;
}
function fetchUserList() {
list.users_data = [];
$http.get(url).then(function (response) {
var res = response.data;
if (res.status === 200) {
angular.forEach(res.data, function (staff) {
list.users_data.push(staff);
});
} else {
alert('Oops! Somethings went wrong!');
}
});
}
});
Now in your controller1
app.controller('controller1', function ($scope, $http, globalServices) {
$scope.createFuntion = function(){
$http.post(url, $.param(some_object)).then(function(response){
//Handle something ...
globalServices.fetchUserList();
});
}});
and in your controller2
app.controller('controller2', function ($scope, $http, globalServices) {
$scope.users = globalServices.getUserList();
});
Now your $scope.users listen to every change in your user_data.
Access the array thru $scope.users.users_data
First project in AngularJS and I started creating my services (factories) that I made modular like this
angular.module('app.services.public', [])
.factory('publicService', ['$http', function publicService($http) {
var results = {};
results.contact = function (name, email, message){
return $http.get();
};
return results;
}]);
That I then call in my main angular app by including it. When I call it, I need to listen for success or error
publicService.contact().success(callback).error(callback)
My question is, I'm going to be doing a lot of API requests through these services and seems to be bad code to listen to the error everytime since 90% of the time it will do the same thing.
How can I create a wrapper around the $http.get or around all factory calls?
So something like
apiCall = function (url, data, successCallback, errorCallback){
$http.get(url,data).success(function(){
successCallback()
}).error(function(){
if(errorCallback()){ errorCallback(); return; }
// or display general error message
})
}
I would recommend against converting promise-based into callback-based APIs. Angular adopted promises and it best to stay with them.
Also, stay away from $http-specific .success/.error and use promise .then/.catch APIs.
How wide do you need to cast your net to handle $http errors?
1) Say, it only applies to your publicService service, then you can "handle" it at the each function:
.factory("publicService", function($http, $q){
function handleError(){
// invokes error handlers
}
return {
onError: function(cb){
// register error handlers
},
doSomethingA: function(){
return $http.get("some/url/A")
.then(function(response){
return response.data;
})
.catch(function(error){
handleError(error);
return $q.reject(error); // still "rethrow" the error
}
},
doSomethingB: function(){
// similar to above
},
// etc...
};
})
Then you could separate request from error handling:
.controller("MainCtrl", function($scope, publicService){
publicService.onError(function(error){
$scope.showError = true; // or something like that
})
})
.controller("FunctionACtrl", function($scope, publicService){
publicService.doSomethingA()
.then(function(data){
$scope.data = data;
});
})
2) Of course, the above, would only apply to request made via publicService. If you want to catch all $http errors, you could implement an $http interceptors. I won't go into detail - there is enough info in documentation and elsewhere - but it would could work like below:
.factory("ErrorService", function(){
return {
onError: function(cb){
// register error handlers
},
broadcastError: function(error){
// invoke error handlers
}
};
})
Then in interceptor, use ErrorService as a dependency:
'responseError': function(rejection) {
ErrorService.broadcastError(rejection);
return $q.reject(rejection);
}
Then you could handle the errors globally:
.controller("MainCtrl", function($scope, ErrorService){
ErrorService.onError(function(error){
$scope.showError = true; // or something like that
})
})
You have the right idea. You can do it easily with a Factory.
myApp.factory(APIService, function(publicService, $http) {
return {
// create methods in here
...
contact: function(cb) {
$http.get(url,data).success(cb).error(function(err){
console.error('oh no!', err);
});
}
};
});
Then you can use it in your controllers.
APIService.contact(function(data){
console.log('response from the api!', data);
});
You can even move your error handler to its own factory as well.
I would suggest an implementation using angular's $q service.
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var results = {};
results.contact = function (name, email, message){
return $q.when($http.get());
};
return results;
}]);
Or rather than use the $q.when(...) method you can use $q.deferred like so:
angular.module('app.services.public', [])
.factory('publicService', ['$http', '$q', function publicService($http, $q) {
var deferred = $q.deferred();
var results = {};
results.contact = function (name, email, message){
$http.get().success(function(data){
deferred.resolve({
// assumes data retried from http request has a title and price attribute
title: data.title,
cost: data.price});
}).error(function(data){
deferred.reject(data);
});
};
return deferred.promise;
}]);
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 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 have this endpoints
/clients/:id
/bills/:id
/clients/:id/bills
I'm trying to create some resources with angular-resource to represent my API.
Clients and Bills Resources
I created a resource for the clients,
.factory('Clients', function($resource){
return $resource('/clients/:id')
})
.factory('Bills', function($resource){
return $resource('/bills/:id')
});
Those worked fine.
The Problem
My problem is when I wanted to define a resource to represent the bills of a client calling the endpoint /client/:id/bills
I thought that this should be a Bills resource with a method getFromClient() or something like that, as it will return an array of Bills from the client. But I have already use the Bills name. And the endpoint is different to the one already defined.
Any idea how to structure this?
I think what I was loooking for is now in Anguar 1.1
.factory('Bills', function($resource){
return $resource('/bills/:id',{}, {
get: {
method: 'GET',
params:{
clientId: '#clientId'
}
},
'getFromClient': {
method:'GET',
params: {
clientId: '#clientId'
},
url: host + "/clients/:clientId/bills",
isArray: true
}
})
});
Now you can add a url property to the method declaration to override the main url.
If you want to go with a library that enable you to solve this problema and many others, you could try https://github.com/platanus/angular-restmod
Here is an example:
.factory('Client', function(restmod){
return restmod.model('clients', {
bills: { hasMany: 'Bill' }
});
}
.factory('Bill', function(restmod){
return restmod.model('bills');
}
.controller('myController', function(Client){
$scope.client = Client.$find(1);
$scope.client.bills.$search();
});
Check the ngResource docs... Scroll down to the first example heading:
http://docs.angularjs.org/api/ngResource.$resource
It's kinda confusing because they're not doing it right in the example... lol
I believe it would look something like:
.factory('Clients', function($resource){
return $resource('/clients/:id', {id:'#id'})
})
.factory('ClientBills', function($resource){
return $resource('/clients/:clientId/bills/:id', {clientId:'#clientId', id:'#id'})
});
I haven't tested this, but I think that's right. =)
UPDATE
You would then access them like so:
.controller('MyCtrl', function ($scope, $routeParams, Clients, Bills) {
$scope.client = Clients.get({id:$routeParams.clientId})
$scope.bills = ClientBills.get({clientId:$routeParams.clientId})
})
I also changed the name of the service to "ClientBills", as it is more specific to the task, and you may want a "Bills" service that doesn't require a client id...