I have a simple RESTful service like the following:
services.factory('UserService', ['$resource, function() {
return $resource('...');
}]);
This way I have to invoke like this:
UserService.get({id: userId}, function(response) {
// do something.
});
I wanted to be able to do something like this:
UserService.get(userId).then(function(response) {
// do something with data
});
Is it possible? I am struggling with this and end up always having to use $promise.then() in my controllers. I wanted to "hide" that $promise in my RESTful service.
$resource purposely exposes the promise through ... well ... $promise, so you can use the UserService to abstract this:
services.factory("UserService", ["$resource", function ($resource) {
var userService = $resource("...");
return {
get: function (id) {
return userService.get({id: id}).$promise;
}
}
});
According to Todd Motto you could encapsulate your service somewhat like this :
"We create an Object with the same name inside the function. This can aid documentation as well for comment-generated docs."
function AnotherService () {
var AnotherService = {};
AnotherService.someValue = '';
AnotherService.someMethod = function (idParam) {
return $resource(apiUrl + ":action/:id", {}, {
get: {method: 'GET', params: {id:idParam, action: 'get'}
};
return AnotherService;
}
angular.module('app')
.factory('AnotherService', AnotherService);
Related
In order to better consume a hateoas enabled rest api I got the idea to intercept http calls and add some methods to my resource objects before returning them.
The idea is that if the resource has an array of links I'll add methods to easy further http requests.
The below code is push to the interceptors array of a $httpProvider and does pretty much what I want.
define(['angular'], function (angular) {
var $http = angular.injector(['ng']).get('$http');
function flattenLinks(links) {
for (var key in links) {
var rel = links[key].rel;
var href = links[key].href;
links[rel] = href;
}
}
return {
'response': function responseHandler(response) {
var data = response.data ? response.data : response;
if(typeof data === 'string' || !data.hasOwnProperty('links')) {
return response;
}
if(data.links instanceof Array) {
flattenLinks(data.links);
}
if(data instanceof Array) {
for(var key in data) {
responseHandler(data[key]);
}
}
data.hasLink = function(link) {
return link in data.links;
};
data.get = function(rel) {
return $http.get(data.links[rel]);
};
data.delete = function(rel) {
return $http.delete(data.links[rel]);
};
data.post = function(rel) {
return $http.post(data.links[rel], data);
};
data.put = function(rel) {
return $http.put(data.links[rel], data);
};
return response;
}
};
});
The problem is that when I use, as seen below, my added methods to do requests the response isn't handled by my interceptor. The $http.get, $http.delete, etc. done from within my interceptor isn't intercepted (naturally!).
vm.someResourceObj.get('self').then(function(response) {
console.log(response);
});
So the question is. How do I get the internal calls to $http handled?
First of all - this is not an answer to you question =)
But it's a kind of advice: man, you are really try to develop a bicycle.
According to developers guide you should use $resource instead of $http to interact with REST api. Especially with RESTful.
$resource service will provide you functionality to make exactly what you want - to make requests like User.get() and after this User.save(), User.update(), etc.
My suggest is to create a factory for each entity(like "user", "account", etc), or for each workflow(like "sign up").
'use strict';
angular.module('app.factories.user', ['ngResource'])
.factory('UserFactory', function ($resource) {
var _userApi = {
user: $resource('/user', {}, {})
};
return {
getCurrentUser: function () {
return _userApi.user.get(function (data) {
// here we got data.user;
}, function (response) {
console.error(response.status);
}).$promise;
}
};
})
;
And use it in a controller:
.controller('UserPageCtrl', function ($scope, UserFactory) {
var currentUser;
$scope.getCurrentUser = function () {
return UserFactory.getCurrentUser().success(function (user) {
//here we got user
currentUser = user;
//now we able do stuf like
//currentUser.save()
//etc.
}).error(function (err) {
console.error(err);
});
};
});
So, don't mind if examples above little bit... you know... let's say if you don't like it ))
Just take a look at $resource documentation (and this)
If you don't like resource, you can use third-party restanguar (or perhaps something else). But restangular is much more strict, so choose it only if you know for sure that your backend developers aren't idiots.
UPD1: About interceptors (I'll put here my working code for error intercepting, the idea of this code is to logout user when any request return 403 error status):
You are able to add all facctory to global $httpProvider(at module config section).
Like this:
app.config(function ($urlRouterProvider, $httpProvider) {
//Process backend errors
$httpProvider.interceptors.push('errorsFactory');
})
And here is errorsFactory:
'use strict';
angular.module('app.factories.errors', [])
.factory('errorsFactory', function ($q) {
var HTTP_STATUS = {
FORBIDDEN: 403
};
return {
response: function (response) {
if (response.status === HTTP_STATUS.FORBIDDEN) {
//logout here
}
return response || $q.when(response);
},
responseError: function (rejection) {
if (rejection.status === HTTP_STATUS.FORBIDDEN) {
//logout here
}
return $q.reject(rejection);
}
};
})
;
Use $injector to get $http instead of accessing it via angular..
Detailed Answer:
The problem is how you inject $http. It seems that you get some $http that is outside of your app (I can't exactly say why, though). var $http = angular.injector(['ng']).get('$http'); doesn't do what you want (or what you think it does).
You could do var $http = angular.element(document).injector().get('$http'); (or probably better use $document there), but imo you should use the $injector-service normally: you can just add $injector to your dependencies/inject statement and inside your interceptor use that to retrieve the service you want like: var $http = $injector.get("$http");
With that your interceptor will intercept the $http-calls it makes.
I am starting to learn angularjs, so far i can create update delete withoud using services. I am trying to take it to the next level: Ive created a service page that looks like this:
app.factory('MainService', function($http) {
var getFeaturesFromServer = function() {
return $http.get('restfullfeatures/features');
}
var deleteFeature = function(id) {
return $http.post('restfullfeatures/delete', {'id': id});
}
var createFeature = function(feature) {
return $http.post('restfullfeatures/create', {'title': feature.title, 'description': feature.description});
}
return {
getHello : getHello,
getFeatures: getFeaturesFromServer,
deleteFeature: deleteFeature,
createFeature: createFeature
}
});
and my add function in controller looks like this:
$scope.add = function(){
MainService.createFeature($scope.formFeature)
.then(function(response) {
console.log('feature created',response.data);
$scope.features.push($scope.formFeature);
$scope.formFeature = {};
}, function(error) {
alert('error',error);
});
};
And this is my postCreate function:
public function postCreate()
{
\App\Feature::create(
['title' => \Input::get('title'),
'description' => \Input::get('description')
]);
return ['success' => true];
}
I have a table in my database called features, so basically what i am trying to do is add a new feature to my table using angularjs, my controller doesnt seem to recognize formFeature all i get is 'undefined' then i get the error: Cannot read property of type undefined, but I am using it in my delete function and it works perfectly, what did i miss here??
Factory
So when creating a factory for CRUD, try to lay out your factory like the example below. The example is some code I wrote for a project each call willl do a different thing, the idea is that you add a method that you can instantiate when you add the factory to your controller. (note: don't use $rootScope for session)
.factory('Chats', function ($http, $rootScope, $stateParams) {
return {
all: function () {
return $http.get('http://your_ip/chats', { params: { user_id: $rootScope.session } })
},
get: function () {
return $http.get('http://your_ip/chat', { params: { user_id: $rootScope.session, chat_id: $stateParams.idchat } })
},
add: function (id) {
return $http.post('http://your_ip/newChat', { params: {idfriends:id}})
}
};
});
Controller
when you instantiate this in your controller your looking at something like this
.controller('ChatsCtrl', function ($scope, Chats) {
Chats.all().success(function (response) {
$scope.chats = response;
});
$scope.getChat = function (id) {
Chats.get().success(function (response) { })
};
$scope.addChat = function (id) {
Chats.add(id).success(function (response) { })
};
})
$scope.remove and $scope.addChat and linked to buttons that will execute on click and $scope.chats is bound to the page via an ng-repeat.
Conclusion
Clean up your factories to look like this and you can easily write a series of reusable methods that are easy to instantiate in your controller.
I'm following the Tutorial from the official AngularJS docs and I want to know if I can add another function to the Phone factory so that I can organize code better. They have declared a "query" function, but what if I wanted to add a query2 function that references a different url...say phones2/:phoneName.json for example?
Factory declaration:
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
I have tried a number of things and non of them seem to be working :s
This answer seems to be on the right track, but the syntax for each factory function doesn't quite match up with the above factory.
Something along the lines of:
phonecatServices.factory('Phone', ['$resource',
function($resource){
return {
query: ...
query2: ...
}
}]);
One such example of this is:
Link for Demo
angular.module('services', []).factory('factoryName', ["$filter",
function($filter) {
var method1Logic = function(args) {
//code
};
var method2Logic = function(args) {
//code
};
return {
method1: method1Logic,
method2: method1Logic
};
}
]).controller('MainController', ["$scope", "$rootScope", "$filter", "factoryName", function ($scope, $rootScope, $filter,factoryName) {
$scope.testMethod1 = function(arg){
$scope.val1 = factoryName.method1(arg);
};
$scope.testMethod2 = function(arg){
$scope.val2 = factoryName.method2(arg);
};
}]);
There is even a better version Opinionated version of this: References
function AnotherService () {
var AnotherService = {};
AnotherService.someValue = '';
AnotherService.someMethod = function () {
};
return AnotherService;
}
angular
.module('app')
.factory('AnotherService', AnotherService);
This is the service code:
myServices.factory('Auth', ['$resource',
function($resource){
return {
Login: $resource(serviceURL + 'login', {}, { go: { method:'POST', isArray: false }}),
Logout: $resource(serviceURL + 'logout', {}, { go: { method:'POST', isArray: false }}),
Register: $resource(serviceURL + 'register', {}, { go: { method:'POST', isArray: false }}),
};
}
]);
And from my controller I just have to add the go() function call to make it work:
Auth.Login.go({ username: $scope.username, password: $scope.password },
I guess I could have named the go function after the method and called it "post()" instead for clarity...
Yes, Of course, you can have multiple functions in an object. The only caveat is your service should return an object. You can have all the valid javascript members in that object as long as you follow object's syntax.
So following is possible
phonecatServices.factory('Phone', ['$resource',
function($resource){
return {
query: ... , // NOTICE THE COMMA HERE
query2: ...
}
}]);
You must be missing the comma (,) to separate your object's key values.
I want to project the result of my $http call into another model in order to make the projection global to the service call.
In other words, the result i get from my http/api call is not using the exact model i want.
How do i do this projection within my service class?
angular.module('openart')
.factory('BritishLibraryApi', ['$http', function ($http) {
return {
getPage:function(page){
return $http({method:"GET",url:'/api/british-library/'+page})
.success(function(data){
//would like to do something here like
return data.results.map(function(i){
//project i into another model here
return {
};
});
});
}
};
}]);
If you create your own promise and resolve to your mapped data
angular.module('openart')
.factory('BritishLibraryApi', ['$http','$q', function ($http,$q) {
return {
getPage:function(page){
var defer=$q.defer();
$http({method:"GET",url:'/api/british-library/'+page})
.success(function(data){
//would like to do something here like
defer.resolve(data.results.map(function(i){
//project i into another model here
return {
};
}));
});
return defer.promise;
}
};
}]);
I'm trying to add a prototypal function to my ngResource factory method like this:
.factory('Magazine', function ($resource) {
var Magazine = $resource('http://localhost/dooleystand/ci/api/magazine/:magId', {
loginID : organEntity,
password : organCommpassword,
magId : "#magId"
});
Magazine.prototype.getLastAdded = function() {
return this.get({magId:"lastAdded"});
};
return Magazine;
})
Then I tried to call this factory method from a function in my controller:
Magazine.save(magazine, function() {
var newMagazine = Magazine.getLastAdded;
var tempMagazine = {
issue_number : newMagazine.issue_number,
magazine_name : newMagazine.magazine_name
};
$scope.magazines.push(tempMagazine);
});
It seems like my browser did not generate a new request at all. Any way to actually accomplish this or do I need to have a separate factory method?
How about extend the resource actions like this?
.factory('Magazine', function ($resource) {
return $resource('http://localhost/dooleystand/ci/api/magazine/:magId', {
loginID: organEntity,
password: organCommpassword,
magId: "#magId"
}, {
getLastAdded: { method: 'GET', params: { magId: 'lastAdded' } }
});
}