AngularJS $resource in service function - javascript

I'm working in a team that is building an Android application using web technologies (angular.js, etc.) and Phonegap to turn the project into an Android application. We're fairly new to AngularJS and have run into a problem integrating services into our code. We are trying to do some basic server calls, which are working as regular code, but we are trying to make them a service so we don't duplicate this all over the place. We're using a Phonegap localStorage plugin to store the ID of a database object on the phone's HTML5 local storage.
Here is our code:
.service("contactServer", function($resource, $http, baseUrl) {
// Initialize resource and modify methods s.t. create POSTS and save PUTS.
this.post = function() {
alert("Starting post");
var item = {"name": model.userName, "position": model.position};
alert("Creating resource");
var serverResource = $resource(baseUrl,
{create: {method: "POST"}, save: {method: "PUT"}});
alert("Created resource");
new serverResource.create(item).then(function(data, status, headers, config) {
alert("id: " + data._id);
window.localStorage.setItem("DBid", data._id);
}, function(data, status, headers, config) {
alert(JSON.stringify(['Error', data, status, headers, config]))
})
}
this.put = function() {
alert("Starting put");
var item = {"name": model.userName, "position": model.position, "id": window.localStorage.getItem("DBid")};
alert("Creating resource");
var serverResource = $resource(baseUrl + "/:id", {id: "#id"},
{create: {method: "POST"}, save: {method: "PUT"}});
alert("Created resource");
new serverResource(item).save().then(function(data, status, headers, config) {
alert(JSON.stringify(['Success', data, status, headers, config]));
}, function(data, status, headers, config) {
alert(JSON.stringify(['Error', data, status, headers, config]));
})
}
})
baseUrl is a URL link to our database. We call the services here:
.run(function(contactServer) {
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
if (window.localStorage.getItem("DBid") == null) {
alert("no DBid");
contactServer.post();
}
else {
alert("Retrieved stored DBid: " + window.localStorage.getItem("DBid"));
contactServer.put();
}
}
})
deviceready is a Phonegap event that fires when the application has loaded on the user's phone. We want to call these services in several of our controllers, but also initially during this run function.
The code fires up to the "starting post" alert after being called in the run function, but then breaks. Are we using $resource wrong? (It is correctly listed as a dependency). Are we implementing the service wrong?

The problem you have is the definition of your method
change this
var serverResource = $resource(baseUrl,
{create: {method: "POST"}, save: {method: "PUT"}});
Into this:
var serverResource = $resource(baseUrl, {},
{create: {method: "POST"}, save: {method: "PUT"}});
Take a look at the documentation
So as yours methods are non Get instances you should execute them like following:
new serverResource.$create(item).then(function(data, status, headers, config) {
//content
}, function(data, status, headers, config) {
//error content
})
The documentation says:
HTTP GET "class" actions: Resource.action([parameters], [success], [error])
non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
non-GET instance actions: instance.$action([parameters], [success], [error])
I would encourage you to use console.log instead of alerts to debug your code, the alert could create some problems with the digest cycle.
Let me give you an alternative solution:
.factory('contactServer', ['$resource', function($resource){
var baseUrl = 'testing/:id';
var resource = $resource(baseUrl, {id: '#id'}, {update: {method: 'PUT'}});
return resource;
}]);
and you would use it as:
.run(function(contactServer) {
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
if (window.localStorage.getItem("DBid") == null) {
alert("no DBid");
//item creation
var instance = new contactServer(item);
contactServer.$save(item).then(function(res){
window.localStorage.setItem("DBid", res._id);
});
}
else {
alert("Retrieved stored DBid: " + window.localStorage.getItem("DBid"));
var id = //theId;
var updateObject = //item update;
var instance = new contactServer();
contactServer.$update({id: id}, updateObject).then(function(res){
console.log('object updated!');
});
}
}
});
..or something like that. Hope this help.

Hope this helps you on the right way:
var app = angular.module("app", ["ngResource"]);
app.service("contactServer", function($resource, $http) {
// Initialize resource and modify methods s.t. create POSTS and save PUTS.
var baseUrl = "test";
var model = {
userName: "",
position: ""
}
var serverResource = $resource(baseUrl + "/:id", {id: "#id"},
{create: {method: "POST"}, save: {method: "PUT"}});
this.post = function() {
alert("Starting post");
var item = {"name": model.userName, "position": model.position};
serverResource.create(item).then(function(data, status, headers, config) {
alert("id: " + data._id);
window.localStorage.setItem("DBid", data._id);
}, function(data, status, headers, config) {
alert(JSON.stringify(['Error', data, status, headers, config]))
})
}
this.put = function() {
alert("Starting put");
var item = {"name": model.userName, "position": model.position, "id": window.localStorage.getItem("DBid")};
serverResource.save(item).then(function(data, status, headers, config) {
alert(JSON.stringify(['Success', data, status, headers, config]));
}, function(data, status, headers, config) {
alert(JSON.stringify(['Error', data, status, headers, config]));
})
}
});
app.run(function(contactServer) {
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
if (window.localStorage.getItem("DBid") == null) {
alert("no DBid");
contactServer.post();
}
else {
alert("Retrieved stored DBid: " + window.localStorage.getItem("DBid"));
contactServer.put();
}
}
});
To make it a bit better I would return serverResource object from the service contactServer and use the resource's save and create methods in the controllers and run block (also resolve promises there).
In short: you have to create the $resource only once with $resource() (outside service function declarations) and just use that in the functions. Also no need for new keyword, might be the thing that breaks this.

Don't you need to change
serverResource.create(item).then(function(data, status, headers, config) {...}
into
serverResource.create(item).$promise.then(function(data, status, headers, config) {...}
--------
see https://docs.angularjs.org/api/ngResource/service/$resource
at the end of the "Credit card resource" example

Actually it's interesting question.
As workaround I would suggest you to use Restangular if you want to build reusable instance for making http queries. In this case it has some advantages:
- It uses promise instead of returning empty object and filling it with new data. (it's actually behaviour of ngResource). Thus you can reuse your services in resolve sections.
- You don't have to create one $resource object per request.
- Support a lot of http methods.
Here is example:
angular.module('app').service('accountService'function(Restangular){
var baseAccounts = Restangular.all('accounts');
this.getAll = function(){
return baseAccounts.getList();
}
this.getById = function(id){
/accounts/profile/{id}
return baseAccounts.one('profile',id)
}
})
More information you can find here. https://github.com/mgonto/restangular#differences-with-resource

Related

Loader not being shown on ajax request in angular js

Since I am using ajax request using $http. It takes a long time since my operation on server takes time. I need to show loader while processing request, but the loader does not show. Although my code seems correct. I tried different methods but did not work.
Index.html
<body ng-app="app">
<!-- loader, can be used on multiple pages-->
<div class="loading loader-quart" ng-show="isLoading"></div>
<!-- my logic -->
</body>
addCtrl.js
//method to get all the attributes and send to server using service
$scope.add = function () {
if ($scope.Option == 'newInstance')
$scope.singleObject.FK_Name = 'MetisEmptyTemplate';
$rootScope.isLoading = true;
var featuresList = websiteService.getUpdatedTree($scope.treeDataSource);
var formData = new Website("", $scope.singleObject.Name, $scope.singleObject.DisplayName, $scope.singleObject.Description, $scope.singleObject.State, "", $scope.singleObject.FK_Name, $scope.singleObject.Email, featuresList);
websiteService.addwebsite(formData);
$rootScope.isLoading = false;
}
websiteService.js
//service to add website
this.addwebsite = function (website) {
$http({
method: 'POST',
url: $rootScope.url + 'Add',
data: JSON.stringify(website),
contentType: 'application/json'
}).success(function (data) {
alert(data);
}).error(function (data, status, headers, config) {
//alert(data);
});
}
Since I am going to turn isLoading as "true" in start and then after request completes I turn isLoading "false". Where is the problem in code?
Your websiteServices code gets executed asynchronously. Which means that the above code would display the loader and then pretty much hide it again instantly.
To handle async code in the controller you must return a promise from the service and put the hiding of the spinner in a callback function using .then().
service:
this.addwebsite = function (website) {
var deferred = $q.defer();
$http({
method: 'POST',
url: $rootScope.url + 'Add',
data: JSON.stringify(website),
contentType: 'application/json'
}).success(function (data) {
alert(data);
deferred.resolve(data);
}).error(function (data, status, headers, config) {
//alert(data);
deferred.reject(data);
});
return deferred.promise
}
controller:
websiteService.addwebsite(formData).then(function(){
$rootScope.isLoading = false
});
this.insertMliveResponse = function(data){
var defer=$q.defer();
var requestURL='/mlive-portlet/rest/mliveResponseService/insertmLiveResponse';
httpRequest(requestURL,data).then(function(data){
defer.resolve(data.data);
},function(data){
defer.reject(data.data);
})
return defer.promise;
}
If you are making request then,
I think the best way to show hide loader is interceptor
In my snippet, I am using loader service to activate/deactivate loader
For Eg:
// http.config.js file
export function httpConfig($httpProvider, AuthInterceptProvider) {
'ngInject';
AuthInterceptProvider.interceptAuth(true);
// added for show loader
$httpProvider.interceptors.push(function (loaderService, $q) {
'ngInject';
return {
'request': function (config) {
loaderService.switchLoaderModeOn();
return config;
},
'requestError': function (rejection) {
loaderService.switchLoaderModeOff();
return $q.reject(rejection);
},
'response': function (response) {
loaderService.switchLoaderModeOff();
return response;
},
'responseError': function (rejection) {
loaderService.switchLoaderModeOff();
return $q.reject(rejection);
}
};
});
}
// and in your module.js file
import {httpConfig} from './config/http.config';
.config(httpConfig)

http.post always entering error callback in IE8

I have a simple Angular service that works fine in FireFox, Safari, Chrome and IE9+
However, for IE8, the service is always hitting the .error callback.
JS:
myService.authUser($scope.data)
.success(function(data, status, headers, config) {
console.log("Success..");
$scope.showProfile = true;
})
.error(function (data, status, headers, config) {
console.log("ERRROR!!!");
$scope.errorText = true;
})
app.service('myService', function($http) {
this.authUser = function (myData) {
return $http({
url: 'url',
method: "POST",
data: angular.toJson(myData),
headers: {
'Content-Type': 'application/json'
}
});
};
});
In the above scenario, IE8 is always logging ERRROR!!!
Make sure that the url you are hitting responds with the correct status code EG : 200, 404 etc
For example if your backend is .NET MVC
public ActionResult TestError(string id) // id = error code{
Response.StatusCode = 400; // Replace .AddHeader
var error = new Error(); // Create class Error() w/ prop
error.ErrorID = 123;
error.Level = 2;
error.Message = "You broke the Internet!";
return Json(error, JsonRequestBehavior.AllowGet);}

Defer execution of page controller until init data is gathered

I have an SPA. I have some basic init data that I'm fetching from the server that I'm certain that I want to defer every page load until that data is loaded. (this data contains whether the user is logged in, permissions, and other vital stuff). So if I have a service for fetching and accessing that data, a page controller might start execution before I have the data, which is bad.
I can't use a promise either, partly because it doesn't solve my problem that I don't want the page to begin loading, and partly because it can't be updated easily and I don't want to always use a promise to fetch this basic data
this is what i've tried so far:
my service
app.factory('AppData', function($q, $http){
var appData = {};
$http
.post( "/api/GeneralActions/getInitData", {
}).success(function (data, status, headers, config) {
appData = data;
}).error(function (data, status, headers, config) {
});
return {
getAppData: function () {
return appData;
}
};
});
my page controller:
app.controller('MainPreferences', function($scope, AppData){
// when this gets executed, appData is null
$scope.appData = AppData.getAppData();
});
Try following snippet
app.factory('AppData', function($q, $http){
var appData = {};
$http
.post( "/api/GeneralActions/getInitData", {
}).success(function (data, status, headers, config) {
//appData = data;
angular.extend(appData, data);
}).error(function (data, status, headers, config) {
});
return {
getAppData: function () {
return appData;
}
};
});
Instead creating appData object again, just extend it with data . By this way your appData object pointer will not change and controllers will also get updated.
Are you using ngRoute? If so, it sounds like what you want is to have a resolve property on your routes to require them to load something before changing the path to the new route.
See the ngRoute docs and search for resolve.
If you are using the stock Angular ngRoute routing system, you can use the resolve property on the route to specify a map of promise-returning functions. The route controller will not be initialized before these promises are all resolved, and as a bonus, the promises' results are injected into the route controller.
For example:
$routeProvider.when('/foo', {
controller: 'fooCtrl',
resolve: {
bar: function($http) { return $http.get('/load/the/bar'); }
}
});
// bar is injected from the route resolve
myApp.controller('fooCtrl', function($scope, bar) {
$scope.bar = bar;
});
I think it should be:
app.factory('AppData', function($q, $http){
var appData = {};
return {
getAppData: function () {
$http.post( "/api/GeneralActions/getInitData", {}).success(function (data, status, headers, config) {
return data;
}).error(function (data, status, headers, config) {
});
}
};
});

JavaScript functions confusion

I am in the process of learning AngularJS, and have the following code (from video tutorials I'm going through).
eventsApp.controller('EventController',
function EventController($scope, eventData) {
eventData.getEvent(function(event) {
$scope.event = event;
});
I am confused about this bit of the JavaScript eventData.getEvent(function(event) { } I admit that my JavaScript knowledge isn't super, but JavaScript functions are so strange in this regard compared to C# or Java functions/methods.
I understand that eventData is my AngularJS service and on that, I have a method called getEvent() but that does not take in an event object as a parameter! Here is my AngularJS service code:
eventsApp.factory('eventData', function($http, $log) {
return {
getEvent: function(successcb) {
$http({ method: 'GET', url: 'data/event/1.json' }).
success(function(data, status, headers, config) {
successcb(data);
}).
error(function(data, status, headers, config) {
$log.warn(data, status, headers, config);
});
}
}
});
Even when I tell myself "ignore object types and classes (thinking along C# / Java lines) and think of it as a plain object", then still I don't get where the event parameter in eventData.getEvent(function(event) { } came from!
Can someone please clarify?
Thanks
You're not actually passing event as the parameter, you're passing that entire function as the parameter as a callback. When getEvent reaches .success, it's calling that function (called successcb in the service), and passing data (where data is actually the event parameter that you're seeing in your controller.
It actually looks more like this:
eventsApp.factory('eventData', function($http, $log) {
return {
getEvent: function() {
$http({ method: 'GET', url: 'data/event/1.json' }).
success(function(data, status, headers, config) {
// successcb(data);
// this is essentially what's happening, except in your controller...
// ...so, this won't actually work because you don't have $scope in this service
// but it's the same idea
successcb(data);
var successcb = function(event) {
$scope.event = event;
};
}).
error(function(data, status, headers, config) {
$log.warn(data, status, headers, config);
});
}
}
});
Javascript functions can be treated as inline objects and can be passed as parameters.
for example you can have something like the below (pseodo code):
var x = function fun(){alert ('hi');};
callerFunction(x);
which is same as
callerFunction(function fun(){alert ('hi');});
Above is what is the concept behind and all modern JS libraries (like jQuery) leverage it to include all callback functions as parameters.
Your service method is waiting for variable REFERENCE_TO_FUNCTION.
Maybe this example will make it more clear:
eventsApp.factory('eventData', function($http, $log) {
return {
getEvent: function(REFERENCE_TO_FUNCTION) {
$http({ method: 'GET', url: 'data/event/1.json' }).
success(function(data, status, headers, config) {
REFERENCE_TO_FUNCTION(data);
}).
error(function(data, status, headers, config) {
$log.warn(data, status, headers, config);
});
}
}
});

Sending JSON using $http cause angular to send text/plain content type

I just want to send the following JSONobjects to my API backend:
{
"username":"alex",
"password":"password"
}
So I wrote the following function, using Angular $http:
$http(
{
method: 'POST',
url: '/api/user/auth/',
data: '{"username":"alex", "password":"alex"}',
})
.success(function(data, status, headers, config) {
// Do Stuff
})
.error(function(data, status, headers, config) {
// Do Stuff
});
I read in documentation for POST method that Content-Type header will be automatically set to "application/json".
But I realized that the content-type I receive on my backend (Django+Tastypie) api is "text/plain".
This cause my API to not respond properly to this request. How should I manage this content-type?
The solution I've moved forward with is to always initialize models on the $scope to an empty block {} on each controller. This guarantees that if no data is bound to that model then you will still have an empty block to pass to your $http.put or $http.post method.
myapp.controller("AccountController", function($scope) {
$scope.user = {}; // Guarantee $scope.user will be defined if nothing is bound to it
$scope.saveAccount = function() {
users.current.put($scope.user, function(response) {
$scope.success.push("Update successful!");
}, function(response) {
$scope.errors.push("An error occurred when saving!");
});
};
}
myapp.factory("users", function($http) {
return {
current: {
put: function(data, success, error) {
return $http.put("/users/current", data).then(function(response) {
success(response);
}, function(response) {
error(response);
});
}
}
};
});
Another alternative is to use the binary || operator on data when calling $http.put or $http.post to make sure a defined argument is supplied:
$http.put("/users/current", data || {}).then(/* ... */);
Try this;
$http.defaults.headers.post["Content-Type"] = "application/json";
$http.post('/api/user/auth/', data).success(function(data, status, headers, config) {
// Do Stuff
})
.error(function(data, status, headers, config) {
// Do Stuff
});

Categories

Resources