Override target server when using $http, $resource in AngularJS on runtime - javascript

I have multiple services providing access to api controllers such as:
app.service('adminSvc', function($resource) {
return {
getTextSample: function() {
return $resource('/admin/TextGenerator/GetTextSample').get();
}
}
});
Is there better way than providing server in variable?
app.service('adminSvc', function($resource) {
return {
getTextSample: function(server) {
return $resource(server + '/admin/TextGenerator/GetTextSample').get();
}
}
});

you can create configuration server that holds all your base URIs and credentials that can be injected where needed, or could have a communications server that handles all requests, and takes the relative, path, method and parameters as arguments and issues the requests and returns a promise to be handled on the other services. both methods allow you to abstract layers the second allows you to decouple your communication and provides more flexibility if you decide to switch providers since your services will only handle promises and no actual communications, they would be able to work with network responses, memory data, or files data

I have recently faced such problem, I solved this by listing all my urls and used $interpolate service to resolve all urls by using a certain base url.
Something like this, creating a Utils service with a method stringResolver() that interpolates each property value and assigning it to the REST services URLS property.
.factory('Utils', function($interpolate) {
return {
stringResolver: stringResolver
};
function stringResolver(objectStrings) {
var string, index;
for(index in objectStrings)
objectStrings[index] = $interpolate(objectStrings[index])(objectStrings);
return objectStrings;
}
})
.factory('REST', function(Utils) {
var REST = {},
URLS = REST.URLS = Utils.stringResolver({
BASE: 'http://my-base-url.com',
AUTHORIZATION: '{{BASE}}/oauth/access_token',
REFRESH_AUTHENTICATION: '{{BASE}}/refresh',
LOGIN: '{{BASE}}/login',
REGISTER: '{{BASE}}/register',
USER: '{{BASE}}/user',
USERS: '{{BASE}}/users/:user_id',
MODULES: '{{BASE}}/modules/:module_id',
READINGS: '{{MODULES}}/readings/:reading_id',
QUESTIONS: '{{READINGS}}/exam/questions/:question_id',
CHOICES: '{{QUESTIONS}}/choices/:choice_id'
});
REST.LOGIN = $resource(REST.URLS.LOGIN);
REST.REGISTER = $resource(REST.URLS.REGISTER);
return REST;
});

Related

Using factory to expose a simple API

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
});

AngularJS : How can we broadcast promise to different controllers?

How can we broadcast updated data from factory.
Am having two factories and two controllers.
Service1: updates the data into db
app.factory('uac',function($http,$q, $cookies)
{
formData.uClient = function(data)
{
var client = $http.post('add_client/',data)
return $q.all({'response':client})
}
}
service 2: gets list of clients from db
app.factory('adService',function($http, $q)
{
clientData = {}
clientData.getListofClients = function()
{
var listOfClients = $http.get('get_clients/')
return $q.all({'listOfClients':listOfClients})
}
return clientData
})
controller 1: sends data to be updated to service
app.controller('acController', function($scope,uac)
{
$scope.clientDatadata = {name:"abcd"}
uac.uClient($scope.clientData)
}
controller 2: gets clients data from service
app.controller('getDetailsController',function($scope,adService)
{
adService.getListofClients().then(function(data)
{
$scope.clientList = data.listOfClients.data
console.log($scope.clientList)
},
function(error)
{
console.log("Can fetch data")
});
}
First I will call getDetailsController to get all clients and whenever a new client is added by calling "acController" i want the updated list of clients in "$scope.clientList".How can i get it done?
You can $emit on event from the sender controller and listen for it in the receiver controller. Since I'm not sure about the hierarchy of your controllers, I'll listen on $rootScope:
controller 1: sends data to be updated to service
app.controller('acController', function($scope,uac)
{
$scope.clientDatadata = {name:"abcd"}
uac.uClient($scope.clientData).then(function(res){
$scope.$emit('serviceUpdated', res); // you can send data if you wish
});
}
controller 2: gets clients data from service
app.controller('getDetailsController',function($scope,adService,$rootScope)
{
function getList(){
adService.getListofClients().then(function(data)
{
$scope.clientList = data.listOfClients.data
console.log($scope.clientList)
},
function(error)
{
console.log("Can fetch data")
});
}
getList();
$rootScope.$on('serviceUpdated', getList);
}
There are three ways to go for this:
(easy) Create a factory which will contain the list of clients and use it directly on your view.
-
myApp.factory('clientList', function () {
return [];
});
call controller 2 to get the list of clients and save on the factory:
app.controller('getDetailsController',function($scope,adService, clientList)
{
adService.getListofClients().then(function(data)
{
clientList = data;
},
function(error)
{
console.log("Can fetch data");
});
}
controller 1: sends data to be updated to service and saves the returned data on the clientList factory
app.controller('acController', function($scope,uac, clientList)
{
$scope.clientDatadata = {name:"abcd"};
clientList = uac.uClient($scope.clientData);
}
The issue about this solution is that in a complex app you wouldn't want to have the data exposed via a factory.
Implement getters and setters on the factory so you don't expose the data to the views but rather have them controlled by the controllers (using $watch).
Use $emit as explained on the answer by #Shomz. But, I would propose to make another get list request after the emission is received. I'd rather broadcast the updated data(in this case the response of the post/update request) and than update the variables that are bound to views.

angularJS services - return promise for retrieving data AND object for managing data?

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.

Cache from $resource service with specific querys

I'm extending $resource with some methods, which using query, just for example:
var Resource = $resource(url, {}, {
query: {
cache: true
}
})
Resource.getByCategory = function (categoryId) {
return Resource.query({ categoryId: categoryId })
}
Resource.getBySearch = function (text) {
return Resource.query({ search: text })
}
I want to cache only the data which comes from getByCategory. as you can see, both methods use query, so I cannot define in the query configuration level - as I'm doing now and the data is cached for the both methods.
Any ideas? I thought about somehow using decorator for cached methods, not sure if it is going to help.

AngularJs ngResource for nested api resurces with different endpoints

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...

Categories