POST new value using ng-change - javascript

I'm attempting to fire off a POST request upon selection of an option within my Angular app. Below is my current code.
HTML:
<select ng-options="option for option in linked.maxOptions" ng-model="linked.selectedMax" ng-change="linked.changeMax()"></select>
Controller:
var playerId = $routeParams.playerId;
vm.changeMax = function() {
playersService.setMaxPoints({
playerId: playerId,
max: vm.selectedMax
}).$promise.then(function(res){
return res.success;
}, function(res) {
alert('Couldn\'t update number of points to ' + vm.selectedMax + ':' + res.success);
});
};
Service:
angular.module('gameApp')
.factory('playersService', ['$resource',
function($resource) {
var base = '/api/players/:playerId/';
return $resource(base, {}, {
setMaxPoints: {method: 'POST', url: base + 'maxPoints/' + ':max'}
});
}]);
The problem is that my parameters are not being passed to my service method for some reason as it attempts to hit this endpoint:
http://localhost:3010/api/players/maxPoints

Where does playerId come from? It's not declared nor passed as a parameter to your changeMax function.
Here is how I declare resources. The syntax is a bit easier than yours so it's less error prone:
angular.module('myApp')
.factory('ActivityData', function($resource) {
return $resource('/service/data/:userEmail/:dataType', {dataType: 'all'},
{
getTotals: {method:'GET', cache: true, params: { dataType: 'total'}}
}
});

The issue was in my controller. I was handling a POST request just like I handle GET requests which apparently does not work. I needed to pass a second, in this case empty, object to my service method call to get things working. I believe this is where you would pass any 'body' of data to your service call:
vm.changeMax = function() {
playersService.setMaxPoints({
playerId: playerId,
max: vm.selectedMax
}, {}).$promise.then(function(res){
return res.success;
}, function(res) {
alert('Couldn\'t update number of points to ' + vm.selectedMax + ':' + res.success);
});
};

Related

Could not load value into angular-bootstrap typeahead values with resources

I have a problem with angular-ui typeahead component. It does not show values populated by angular resources, however using $http works well. I suppose I missing some trick here with asycn call and correct population of returned values.
Working code
$scope.searchForContact = function(val) {
return $http.get('/api/contacts/search', {
params: {
q: val
}
}).then(function(response){
return response.data.map(function(item){
return item.name;
});
});
};
Not working code
$scope.searchForContact = function(val) {
return Contact.search({q: val}, function(response){
return response.map(function(item){
return item.name;
});
});
});
...
'use strict';
app.factory("Contact", function($resource, $http) {
var resource = $resource("/api/contacts/:id", { id: "#_id" },
{
'create': { method: 'POST' },
'index': { method: 'GET', isArray: true },
'search': { method: 'GET', isArray: true, url: '/api/contacts/search', params: true },
'show': { method: 'GET', isArray: false },
'update': { method: 'PUT' },
'destroy': { method: 'DELETE' }
}
);
return resource;
});
Pug template code
input.form-control(
type='text'
ng-model='asyncSelected'
uib-typeahead='contact for contact in searchForContact($viewValue)'
typeahead-loading='loadingLocations'
typeahead-no-results='noResults'
)
i.glyphicon.glyphicon-refresh(ng-show='loadingLocations')
div(ng-show='noResults')
i.glyphicon.glyphicon-remove
|
|No Results Found
Angular resources are working fine, including search endpoint - I just output on page result returned by the search endpoint. In both results should be just an array with string values. What am I doing wrong?
The difference between $http.get and your Contact.search is that the first one returns a promise and the latter doesn't. Any $resource method will usually be resolved to the actual response. I'll show that with an example.
Getting data with $http
var httpResult = $http.get('http://some.url/someResource').then(function(response) {
return response.map(function(item) { return item.name });
});
The httpResult object contains a promise, so we need to use then method to get the actual data. Moreover, the promise will be resolved to the mapped array, which is the expected result.
Getting data with $resource
var someResource = $resource('http://some.url/someResource');
var resourceResult = someResource.query(function(response) {
return response.map(function(item) { return item.name });
});
The resourceResult isn't a promise here. It's a $resource object which will contain the actual data after the response comes from the server (in short, resourceResult will be the array of contacts - the original, not mapped, even though there is a map function). However, the $resource object contains a $promise property which is a promise similar to one returned by $http.get. It might be useful in this case.
Solution
I read in documentation that in order to make uib-typehead work properly, the $scope.searchForContact needs to return a promise. Instead of passing the callback function to search, I would simply chain it with the $promise from $resource object to make it work.
$scope.searchForContact = function(val) {
return Contact.search({q: val}).$promise.then(function(response){
return response.map(function(item){
return item.name;
});
});
});
Let me know if it works for you.

How to do $state.go() with params?

I have perfectly initialized $stateProvider and I'm using all this states with ui-sref. Works great.
User presses the button and thorugh the $stateProvider goes to the edit page.
On this page I have a form which does $http request:
this.pushData = function (data) {
$http.post('/data/' + $stateParams.dataId + '/otherdata', JSON.stringify({
id: otherdata.id,
name: otherdata.name
}), configAuth).then(
function success(response) {
var addedData = response.data;
$.notify({message: addedData.name + " has been added"},{type:'success'});
$state.go('roleInfo({roleId: $stateParams.roleId})');
},
function error(data) {
console.log(data);
$.notify({message: data.data.message},{type:'danger'});
}
);
}
And I want to do redirect to other view if everything is fine. But this:
$state.go('roleInfo({roleId: $stateParams.roleId})');
doesn't work. How can I do $state.go with params?
The way you are trying that would work with ui-sref directive, while calling it using $state.go method, you should pass 1st parameter is stateName & then pass params in Object format.
$state.go('roleInfo', {roleId: $stateParams.roleId});
Try this:
$state.go('myStateName', {roleId: $stateParams.roleId});
You can read the docs here
i have changed
$state.go('roleInfo({roleId: $stateParams.roleId})'); to
$state.go('roleInfo',{roleId :$stateParams.roleId});
this will work
this.pushData = function (data) {
$http.post('/data/' + $stateParams.dataId + '/otherdata', JSON.stringify({
id: otherdata.id,
name: otherdata.name
}), configAuth).then(
function success(response) {
var addedData = response.data;
$.notify({message: addedData.name + " has been added"},{type:'success'});
$state.go('roleInfo',{roleId :$stateParams.roleId}); },
function error(data) {
console.log(data);
$.notify({message: data.data.message},{type:'danger'});
}
);
}

Not understanding promise in angularjs

I thought I was doing well with AngularJS & REST until I came across a problem where my data service was not returning data back in time for my model to use it.
So, I have looked at and implemented a promise, but it is still not delaying until the HTTP REST call is done.
I would appreciate any help from the enlightened.
My routing passes a booking Id to the bookingController, which retrieves the booking details and then displays them in an editable form in booking.html template.
testLabApp.controller('bookingController', function ($q, $scope, $location, $window, $routeParams, service) {
if ($routeParams.id) {
$scope.bId = $routeParams.id;
//Expected this to setup my promise
var bookdefer = $q.defer();
bookdefer.promise
.then(function (booking) {
//When successful the result is assigned to my booking in the controller $scope
$scope.booking = booking;
//I am making a copy for form updates
$scope.editableBooking = angular.copy($scope.booking);
//When it runs console displays error:-
// "TypeError: Unable to get property 'Storeno' of undefined or null reference"
console.log("[bookingCtrl] 1 New: " + $scope.booking.Storeno + " and Editable: " + $scope.editableBooking.Storeno);
});
// Is this not calling my getBooking service function with the Id passed into my controller?
bookdefer.resolve(service.getBooking($scope.bookingId));
}
else {
//...
}
When code get to '[bookingCtrl] 1...' the console displays an error "TypeError: Unable to get property 'Storeno' of undefined or null reference", which makes me think that the booking data hasn't been retrieved.
then after this message the console displays:
[getBooking] Done = Id: 209 | Store no: 9180 | Description: test | Status: Booked
My data service includes a number of functions which make REST calls:-
testLabApp.factory('service', ['$rootScope', '$http', function ($rootScope, $http) {
var service = {};
$http({
method: 'GET',
url: "/_api/web/lists/GetByTitle('Bookings')/Items?$filter=Id eq '" + bookingId + "'",
headers: {
'Accept': 'application/json; odata=verbose'
},
}).success(function (d) {
var e = d.d.results[0];
booking = {
Id: e['Id'],
Storeno: e['Title'],
BookedBy: e['BookedBy'],
Description: e['Description'],
StartDate: e['StartDate'],
EndDate: e['EndDate'],
Status: e['Status']
};
console.log("[getBooking] Done = Id: " + booking.Id + " | Store no: " + booking.Storeno + " | Description: " + booking.Description + " | Status: " + booking.Status);
return booking;
}).error(function (er) {
console.log("[getBooking] http error : " + er);
});
};
Thanks again for any helps or suggestions.
Regards
Craig
The success function in your service is executed asynchronously. As a consequence, your service actually returns null to your controller. Here's what I've done in all my angular projects:
service.getStuff = function(id) {
var dfd = $q.defer();
$http({
method: 'GET',
url: '/api/some-end-point/id'
}).then(function(data) {
var result = data.data;
dfd.resolve(result);
}, function(error) {
$log.error(error);
dfd.reject(error);
});
return dfd.promise;
}
And my controllers are written like this:
if ($routeParams.id) {
$scope.bId = $routeParams.id;
//Expected this to setup my promise
service.getStuff($routeParams.id).then(
function(data){
$scope.booking = data;
},
function(error){
console.log(error);
}
);
}
'Below is an example of an asynchronous get call from a controller using a service. You can actually return $http.get() to your controller in order to not need to create a new deferred object:
(function(){
angular.module('app', []);
angular.module('app').factory('service', ['$http', function($http){
return {
getData: getData;
}
function getData(){
return $http.get('/url')
.success(function(data){
//Do business logic here
return data;
})
.error(function(error){
return error;
});
}
}]);
angular.module('app').controller('controller', ['$scope', 'service', function($scope, service){
service.getData()
.success(function(data){
$scope.data = data;
})
.error(function(error){
//error logic
});
}]);
})();

How to setup CURD with KendoUI grid for use with Kendo-Angular and an Angular OData factory?

In a previous project, where I was not using Angular, I setup a Kendo.DataSource that used an OData endpoint as follows:
var userDS = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
url: "/api/Users?$filter=USERGROUPS/any(usergroup: usergroup/ID eq '" + groupData.ID + "')", // only need to expand users for the selected group
dataType: "json", // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
url: function (data) {
// TODO: write UpdateEntity controller method
return "/api/Users(" + groupData.ID + ")";
},
dataType: "json"
},
destroy: {
url: function (data) {
// TODO: write Delete controller method
return "/api/Users(" + groupData.ID + ")";
},
dataType: "json"
},
parameterMap: function (options, type) {
// this is optional - if we need to remove any parameters (due to partial OData support in WebAPI
var parameterMap = kendo.data.transports.odata.parameterMap(options);
return parameterMap;
}
},
Now, introducing AngularJS into the mix, I would like to know how to define the read, update and destroy events using my AngularJS factory, where there is no URL.
My factory contracts are setup as follows:
contentTypesFactory.getList()
contentTypesFactory.insert(contentType)
contentTypesFacotry.remove(id)
The first problem I see with .getList() is that it doesn't take in any query string parameters, like $orderby and $inlinecount=allpages which I need for use with the KendoUI Grid. It is inside this factory that the URL is defined, then calls an abstract factory (see below).
I need to somehow pass in the URL and the entity name to my factory from the kendo.datasource url: function (remember, that the grid control will append whatever OData querystring parameters are required).
So, how I would setup the factory to output the data expected for each of the CRUD events.
Data source definition:
$scope.contentTypesDataSource = new kendo.data.HierarchicalDataSource({
type: "odata",
transport: {
read: {
//url: "/api/UserGroups?$orderby=GROUPNAME",
url: '/odata/ContentTypes',
//function (data) {
// pass in the URL to the abstract factory
//},
dataType: "json" // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
},
destroy: {
},
parameterMap: function (options, type) { ...
Abstract repository:
app.factory('abstractRepository', [function () {
// we will inject the $http service as repository service
// however we can later refactor this to use another service
function abstractRepository(repositoryService, whichEntity, odataUrlBase) {
//this.http = $http;
this.http = repositoryService;
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
this.route;
}
abstractRepository.prototype = {
getList: function () {
return this.http.get(this.odataUrlBase);
},
get: function (id) {
return this.http.get(this.odataUrlBase + '/' + id);
},
insert: function (entity) {
return this.http.post(this.odataUrlBase, entity);
},
update: function (entity) {
return this.http.put(this.odataUrlBase + '/' + entity.ID, this.whichEntity);
},
remove: function (id) {
return this.http.delete(this.odataUrlBase + '/' + id);
}
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
}
return abstractRepository;
}]);
ContentTypesFactory.js:
// each function returns a promise that can be wired up to callback functions by the caller
// the object returned from the factory is a singleton and can be reused by different controllers
app.factory('contentTypesRepository', ['$http', 'abstractRepository', function ($http, abstractRepository) {
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
function contentTypesRepository() {
abstractRepository.call(this, $http, whichEntity, odataUrlBase);
}
abstractRepository.extend(contentTypesRepository);
return new contentTypesRepository();
}]);
After looking at kendo-examples-asp-net, I'm thinking that I should do away with the ContentTypesFactory and the abstract repository and call the OData endpoint directly - of course this is relatively easy.
However, my initial reason for creating an Angular repository was so that I could do JS unit testing on the data functions. To retain this feature, how can I call the abstract repository directly from the data source functions, and this the recommended way of accomplishing this?

Default $resource POST data

That might be strange but I need to specify some default POST data for my $resource using the factory method of the module.
Does anyone have an idea of how to do that in AngularJS ?
EDIT :
Well, i want to do something like this :
/**
* Module declaration.
* #type {Object}
*/
var services = angular.module("services", ["ngResource"]);
/**
* Product handler service
*/
services.factory("Product", function($resource) {
return $resource("http://someUrl", {}, {
get : {method: "GET", params: {productId: "-1"}},
update: {method : "POST", params:{}, data: {someDataKey: someDataValue}}
});
});
Where data is the default data for my future POST requests.
This is not really the angular way to do such a thing as you lose data consistency if you do it and it doesn't reflect in your model.
Why?
The resource factory creates the object and uses object instance data as POST. I have looked at the documentation and angular-resource.js and there doesn't seem to be a way to specify any default custom properties for the object being created by resource without modifying angular-resource.js.
What you can do is:
services.factory("Product", function($resource) {
return $resource("http://someUrl", {}, {
get : {method: "GET", params: {productId: "-1"}},
update: {method : "POST"}
});
});
and in your controller:
$scope.product = {}; // your product data initialization stuff
$scope.product.someDataKey = 'someDataValue'; // add your default data
var product = new Product($scope.product);
product.$update();
I think it will depend on how you call the update function. If you read the angular main page's tutorial, under "Wire up a Backend", the mongolab.js provides a 'Project' factory. Copied verbatim:
angular.module('mongolab', ['ngResource']).
factory('Project', function($resource) {
var Project = $resource('https://api.mongolab.com/api/1/databases' +
'/angularjs/collections/projects/:id',
{ apiKey: '4f847ad3e4b08a2eed5f3b54' }, {
update: { method: 'PUT' }
}
);
Project.prototype.update = function(cb) {
return Project.update({id: this._id.$oid},
angular.extend({}, this, {_id:undefined}), cb);
};
Project.prototype.destroy = function(cb) {
return Project.remove({id: this._id.$oid}, cb);
};
return Project;
});
The usage is that you first get an instance of the Project:
project = Project.get({id:1});
Then do an update after some changes:
project.update(someFunction);
In your case, you can change the update to always add the data you need:
Product.prototype.update = function(cb) {
return Product.update({},
angular.extend({}, this, {someDataKey: someDataValue}), cb);
};
Otherwise, you can most likely put the key/value pair in the params:
update: {method : "POST", params:{someDataKey: someDataValue}}
It will be POSTed with the key/value pair in the URL, but most app servers nowadays will throw the pair into the params object anyway.
I think most have missed a tiny gem in the documentation here.
non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
This suggests you can do the following.
var User = $resource('/user');
postData = { name : 'Sunil', 'surname' : 'Shantha' };
var user = User.save({notify:'true'}, postData, function() {
// success!
});
The second parameter when doing a save action (post) is post data.
Wrapper function will work.
function myPost(data) {
return $http.post('http://google.com', angular.extend({default: 'value'}, data))
}
myPost().success(function(response) { ... });
Might this solve your problem?
services.factory("Product", function($resource) {
return $resource("http://someUrl", {}, {
get : {method: "GET", params: {productId: "-1"}},
update: {method : "POST", params:{}, data: {someDataKey: someDataValue}}
});
});
services.factory("DefaultProduct", function(Product) {
return function(){
return new Product({
data:"default";
});
};
});
services.controller("ProductCTRL",function($scope,DefaultProduct){
$scope.product = new DefaultProduct();
});
You can just merge your params with the default. Everything not available in params will be provided by the default object. Everything available will be overwritten by myParams
services.factory("Product", function($resource) {
return $resource("http://someUrl", {}, {
get : {method: "GET", params: {productId: "-1"}},
update: {method : "POST", params:angular.extend(myDefault, myParams);}
});
});
where myParams would be your list of variables and myDefault your default values as a json object.
You can set default fields on your request by using transformRequest option for your $resource's actions that use the POST method.
For example something like this
function prependTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array
defaults = angular.isArray(defaults) ? defaults : [defaults];
// Append the new transformation to the defaults
return [transform].concat(defaults);
}
ctrl.factory('MyResource', ['$resource', '$http',
function($resource, $http) {
return $resource('/path/to/myresource/:id', {id : '#id'},
{
create : {
method : 'POST',
transformRequest : prependTransform($http.defaults.transformRequest,
function(data, headers) {
return addDefaultField(data);
}
),
},
});
}
]);

Categories