AngularJS - $http POST with Rails 3.2.3 - javascript

I'm building a simple Contact Management app with Crud using AngularJS 1.0.0rc8.
Getting a list of currently existing contacts is no problem, but while attempting to save a new Contact to the server, a new row is created - complete with the correct id, created_at, and updated_at values - but the rest of the models data is ignored.
Here is a screenshot to show what I mean:
As you can see, numbers 4 and 5 were given the Id's but first_name, last_name, and phone_num were not saved to the database.
I am using a $scope.addContact function within the Controller that deals with the object.
Here is the entire code for the Contact List Controller:
'use strict';
function ContactListCtrl($scope, $http) {
$http.get('/contacts').success(function(data) {
$scope.contacts = data;
});
$scope.addContact = function(data) {
$http.post('/contacts/', data).success(function(data) {
console.log(data);
data.first_name = $("#new_contact_first_name").val();
data.last_name = $("#new_contact_last_name").val();
});
this.newFirstName = '';
this.newLastName = '';
};
};
After clicking 'Save' on the new-contact.html partial, the Object is logged to the Console, if I inspect its contents, than sure enough the values are collected - notice Jimi Hendrix is there:
Here is the form as it appears in the new-contact.html template:
<form id="contact_form" ng-submit="addContact()">
<input type="text" id="new_contact_first_name" name="newContactFirstName" ng-model="newFirstName" placeholder="First Name"></br>
<input type="text" id="new_contact_last_name" name="newContactLastName" ng-model="newLastName" placeholder="Last Name"></br>
<input type="button" id="contact_submit_btn" value="Add Contact" class="btn btn-primary">
</form>
The addContact() function is fired after the form is submitted with JQuery:
$(document).ready(function(){
$("#contact_submit_btn").click(function(){
$("#contact_form").submit();
});
});
(Something tells me that I may not be using the ng-model attributes correctly.)
Any ideas on where I am going wrong with this? Or ideas on how I can better go about implementing this design?
Thanks.
UPDATE BELOW:
Here is my entire updated controller code - with help from Sathish:
// contacts controllers
'use strict';
function ContactListCtrl($scope, $http, Contacts) {
$scope.contacts = Contacts.index();
$scope.addContact = function() {
var newContact = {
first_name: $scope.newContactFirstName,
last_name: $scope.newContactLastName
};
var nc = new Contacts({ contact: newContact });
nc.$create(function() {
$scope.contacts.push(nc);
// now that the contact is saved, clear the form data
$scope.newContactFirstName = "";
$scope.newContactLastName = "";
})
}
};
ContactListCtrl.$inject = ['$scope', '$http', 'Contacts'];
function ContactDetailCtrl($scope, $routeParams, Contacts) {
$scope.contact = Contacts.get( {contact_id: $routeParams.contact_id} );
}
ContactDetailCtrl.$inject = ['$scope', '$routeParams', 'Contacts'];
I am now receiving the error: Unknown Provider for Contacts. Here is a screenshot of the error
Ok, I managed to fix that error by providing a ngResource to the main App file. Here's what it looks like:
// main app javascript file
'use strict';
angular.module('contactapp', ['ngResource']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/contacts', {template: 'assets/app/partials/contact-list.html', controller: ContactListCtrl}).
when('/contacts/new', {template: 'assets/app/partials/new-contact.html', controller: ContactListCtrl}).
when('/contacts/:contact_id', {template: 'assets/app/partials/contact-detail.html', controller: ContactDetailCtrl}).
otherwise({redirectTo: '/contacts'});
}]);
I am receiving a new error: WARNING: Can't verify CSRF token authenticity
Alright, managed to fix that problem too by adding a callback to the API controller:
Rails shows "WARNING: Can't verify CSRF token authenticity" from a RestKit POST
Now I am back to the original problem. When the create method is called, a new row is saved to the database, but the models data is not.
Awesome... finally got this thing working.
The problem was the $scope.addContact function. It was using the 'name' of the input instead of the ng-model binding called 'newFirstName' and 'newLastName' that resides in the template.
Here's what the updated function looks like:
$scope.addContact = function() {
var newContact = {
first_name: $scope.newFirstName,
last_name: $scope.newLastName
};
var nc = new Contacts({ contact: newContact });
nc.$create(function() {
$scope.contacts.push(nc);
// now that the contact is saved, clear the form data
$scope.newFirstName = "";
$scope.newLastName = "";
})
}

This can be better implemented using a Contacts service. Please define a Contacts service in app/assets/javascripts/services.js.erb as shown below:
var servicesModule = angular.module('<your app name>',
[<list of modules needed by this app>]);
servicesModule.factory('Contacts', function($resource) {
var ContactsService = $resource('/contacts/:contact_id', {}, {
'create': { method: 'POST' },
'index': { method: 'GET', isArray: true },
'update': { method: 'PUT' },
'destroy': { method: 'DELETE' }
});
return ContactsService;
});
Change the addContact method in the controller as shown below:
function ContactListCtrl($scope, $http, Contacts) {
...
...
...
$scope.addContact = function () {
var newContact = {
first_name: $scope.newContactFirstName,
last_name: $scope.newContactLastName
};
var nc = new Contacts({ contact: newContact });
nc.$create(function() {
$scope.contacts.push(nc);
// now that the contact is saved, clear the form data.
$scope.newContactFirstName = "";
$scope.newContactLastName = "";
});
};
...
...
...
}
ContactListCtrl.$inject = ['$scope', '$http', 'Contacts'];
In addition to this, you can simplify the $http.get(...) part also. You can use Contacts.index();
Note:
If you gave ng-app="MyAppName" then please replace <your app name> with MyAppName.
<list of modules needed by this app> needs to the replaced by a comma-separated list of strings representing any modules needed by your application.

Check the attr_accessible on your model. With new 3.2.3 rails all model attributes became protected from mass-assignment by default.

Related

Posting data from a form using $http (REST)

I'm trying to use Angular for CRUD operations, but I'm having some trouble sending POST requests to the server.
Here's my controller:
angular.module('myModule').controller("ListingCtrl", function($scope, posts) {
$scope.addProject = function () {
if (!$scope.title || $scope.title === '') {
return;
}
posts.create({
title: $scope.title,
short_description: $scope.short_description
});
$scope.title = '';
$scope.short_description = '';
};
});
Here's my service:
angular.module('myModule', [])
.factory('posts', [
'$http',
function($http){
var o = {
posts: []
};
return o;
}]);
o.create = function(post) {
return $http.post('linktomyliveAPI', post).success(function(data){
o.posts.push(data);
});
};
And finally, here's the view:
<div ng-controller="ListingCtrl">
<form ng-submit="addProject()">
<input type="text" ng-model="title"></input>
<input type="text" ng-model="short_description"></input>
<button type="submit">Post</button>
</form>
I've been able to successful make GET requests, but for some reason, I can't figure out POST.
My API was built using Django Rest Framework, if that matters.
Thanks!
I think the way you have added create method is not proper, you have to add that method on factory object
Put the create method on an object returned from factory.
In your service
angular.module('myModule', [])
.factory('posts', ['$http',function($http){
var create = function(post) {
return $http.post('linktomyliveAPI', post).success(function(data){
o.posts.push(data);
});
};
var o = {
posts: [],
// expose create method on factory object
create: create
};
return o;
}]);
Hopefully these will solve your problem.
Fixed it.
I had to change some server-side settings in order to get this to work.

invoking an angular route from angular service to load a new view and controller

I'm trying to invoke a route through and angular service and since I am using $http.post I can't get the route to invoke. I may be going at this all wrong so I'm hoping someone can make a suggestion or point me in the right direction. Initally I have a page load with a controller which once the search command is called it passes a json object with the request to an angular service which then calls webAPI to pass the request onto my other business layers. Here is a logical diagram of the workflow. The response in blue is a new data object being returned to the UI with the users search results.
From my app I have the following routes setup
(function () {
app = angular.module('app', ['ui.bootstrap', 'ngRoute', 'ngAnimate']).value('ngToastr', toastr);
function router($routeProvider) {
$routeProvider.
when('/search/query', {
templateUrl: '../../AngularTemplates/searchResults.html',
controller: 'searchResultCtrl'
}).
otherwise({
templateUrl: '../../AngularTemplates/splashPage.html'
});
}
app.config(['$routeProvider', router]);
//added toaster as factory so it can be injected into any controller
angular.module('app').factory('ngNotifier', function (ngToastr) {
return {
notify: function (msg) {
ngToastr.success(msg);
},
notifyError: function (msg) {
ngToastr.error(msg);
},
notifyInfo: function (msg) {
ngToastr.info(msg);
}
}
});
})();
The initial page calls the controller which has a service dependency
app.controller('searchController', ['$scope', '$filter', 'searchService', 'ngNotifier', '$log', '$timeout', 'searchAttributes' , function ($scope, $filter, searchService, ngNotifier, $log, $timeout, searchAttributes) {
var vm = this;
vm.search = search;
vm.updateEntities = updateEntitySelection;
//bootstraped data from MVC
$scope.userOptions = searchAttributes.mvcData;
//scoped variables
$scope.searchTerm = null;
//ui container for search response
$scope.searchResponse;
$scope.entityList = [
'Search All ',
'Search in Departments ',
'Search in Automotive '
]
$scope.selectedEntity = 'Search All';
function buildSearchRequest() {
var searchResponse = {
searchTerm: $scope.searchTerm,
pageSize: 10,//this will be set by configuration from the UI
pagesReturned: 0,
entityFilter: $scope.selectedEntity
};
return searchResponse;
}
function onError(msg) {
$log.error('An error has occured: ' + msg.data);
}
function updateEntitySelection(entityName) {
$scope.selectedEntity = entityName;
}
function search() {
var request = buildSearchRequest();
searchService.search(request);
}
}]);
and the search service
app.factory('searchService', ['$http', function($http) {
var myEsResults;
function getSearchResults(searchRequest) {
return $http.post('search/query', searchRequest, {}).then(function (response) {
myEsResults = response.data});
}
var getResults = function () {
return myEsResults;
};
return{
search: getSearchResults,
getResults: getResults
};
}]);
What I am trying to accomplish is when the document loads a splash screen is displayed (which works). when the search is executed the request is passed to webapi and then the response is returned as an objectback to the view and a new controller so it can render the search results. I have passed data back and forth between controllers in the past however where I am stuck is using an angular service to call route in webapi. Making this call does not update the page URL and therefore the route is not invoked nor is the second controller loaded to display the results. In the past I have invoked angular routes using a url http://#/route however in this instance I am using an input button with ng-click. I would appreciate any suggestions as to how on the return of data get the 'result view' and controller to load. Is routing the correct approach or is there another way to load the view and controller when using an angular service?
Thanks in advance
<button type="button" class="btn btn-primary btn-lg" ng-click="vm.search()"><span class="glyphicon glyphicon-search"></span></button>
Should be able to do it using $location.path('/search/query')
function getSearchResults(searchRequest) {
return $http.post('search/query', searchRequest, {}).then(function (response) {
myEsResults = response.data;
$location.path('/search/query');
});
}
however workflow seems like it would make more sense to add either routeParams to the url or a search query param and pass url encoded query term to url and make request based on that. Then the request would be made by the searchResultCtrl controller or a resolve in the router config.
Something like:
$routeProvider.
when('/search/query/:queryterm', {
templateUrl: '../../AngularTemplates/searchResults.html',
controller: 'searchResultCtrl'
}).
And path would be generated by:
$location.path('/search/query/' + encodeURIComponent($scope.searchTerm) );

angular promise - on submit gets "Error: args is null $parseFunctionCall"

I am currently following this tuto on MEAN.js : https://thinkster.io/mean-stack-tutorial/ .
I am stuck into the end of "Wiring Everything Up", I am completlty new to angular so I am not pretending I understood everything I did. Here is the situation :
We are using the plugin ui-router.
First here is the html template :
<form name="addComment" ng-submit="addComment.$valid && addComment()"novalidate>
<div class="form-group">
<input class="form-control" type="text" placeholder="Comment" ng-model="body" required/>
</div>
<button type="submit" class="btn btn-primary">Comment</button>
</form>
The error "Error: args is null $parseFunctionCall" occurs only when I submit the form
Then, here is the configuration step for this page :
app.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('posts', {
url : '/posts/{id}',
templateUrl: '/posts.html',
controller : 'PostsCtrl',
resolve : {
post: ['$stateParams', 'posts', function ($stateParams, posts) {
return posts.get($stateParams.id);
}]
}
});
$urlRouterProvider.otherwise('home');
}]);
There, is the controller :
app.controller('PostsCtrl', ['$scope', 'posts', 'post',
function ($scope, posts, post) {
$scope.post = post;
$scope.addComment = function () {
posts.addComment(post._id, {
body : $scope.body,
author: 'user'
}).success(function (comment) {
$scope.post.comments.push(comment);
});
$scope.body = '';
};
$scope.incrementUpVote = function (comment) {
posts.upvoteComment(post, comment);
};
}]);
And Finally, the factory where the posts are retrieved from a remote webservice
app.factory('posts', ['$http', function ($http) {
var o = {
posts: []
};
o.get = function (id) {
return $http.get('/posts/' + id).then(function (res) {
return res.data;
});
};
o.addComment = function (id, comment) {
return $http.post('/posts/' + id + '/comments', comment);
};
return o;
}]);
I've only given the parts that I think are relevant.
I suspect that the problem is comming from the promise and the scope which have been unlinked. I searched about promises but I think that ui-router is doing it differently.
I tried some $watch in the controller but without succeding.
Has anyone some idea about that ? Thank you in advance
The form name addComment (used for addComment.$valid) and the function addComment added to the scope are clashing with each other, rename one or the other.
See the Angular docs for the form directive:
If the name attribute is specified, the form controller is published
onto the current scope under this name.
As you are manually also adding a function named addComment, it is using the wrong one when evaluating the ng-submit.

Angular - Get input on one page and show value on a second page w/ same controller

I'm trying to set up my Angular app to get input on one page, then display it on a second page. I'm using ng-model to tie the two pages together. I was hoping that by using the same controller for both (where the values are stored) pages, that the information would save and display onto the second page, but it is erased.
Is there something I'm doing wrong? Is there a different way to store these input values until the page is refreshed?
Factory:
angular.module('BSG').factory('airplaneFactory', function() {
var name = '';
var last_name = '';
var color = '';
return {
name: name,
last_name: last_name,
color: color
}
})
Airplane Controller:
angular.module('BSG').controller('AirplaneCtrl', function($scope, airplaneFactory) {
'use strict';
$scope.name = airplaneFactory.name;
$scope.last_name = airplaneFactory.last_name;
$scope.color = airplaneFactory.color;
});
Airplane Prompts Controller:
angular.module('BSG').controller('AirplanePromptsCtrl', function($scope, airplaneFactory) {
'use strict';
$scope.name = airplaneFactory.name;
$scope.last_name = airplaneFactory.last_name;
$scope.color = airplaneFactory.color;
});
Routing:
.when('/airplane', {
templateUrl: 'templates/stories/airplane.html',
controller: "AirplaneCtrl"
})
.when('/airplaneprompts', {
templateUrl: 'templates/stories/airplane_prompts.html',
controller: "AirplanePromptsCtrl"
})
Snippet from airplane_prompts.html:
<h2>Who is this story about?</h2>
<p><input type="text" placeholder="name" ng-model="airplane.name"></p>
<h2>What is {{ airplane.name }}'s favorite color?</h2>
<p><input type="text" placeholder="favorite color" ng-model="airplane.color"></p>
Snippet from airplane.html:
Mine was {{ airplane.color }} and had a nametag on it that said {{ airplane.name }}
For each page make a new controller.
Create a separate service to store the value(s).
Each controller uses this service to get/set the value.
Look at the example "Wire up a Backend" on mainpage https://angularjs.org/ (scroll down)
the Projects-factory is your storage provider. Change it from remote storage to local storage with a simple
var myvalue;
$scope.airplane should be created as a service and then you can use dependency injection by inserting the service as a parameter to both controllers.
angular.module('BSG').factory('airplaneFactory', function() {
return {
name: 'name',
last_name: 'last_name',
color: 'color'
}
})

How to get every object in an JSON array in Angular

I'm new in AngularJS, and JS overall. But I think it's fairly simple coming from Java in school.
My first service contained this:
app.factory('User', function($http) {
var user = {
username : null,
company : null,
address : null,
city: null,
country: null
};
$http.get('/webservice/user').success(function(data){
user.username = data.username;
user.company = data.company;
user.address = data.address;
user.city = data.city;
user.country = data.country;
})
return user;
})
I accessed it from the UserCtrl:
app.controller('UserCtrl', ['$scope', 'User', function ($scope, User){
$scope.user = User;
}]);
And in the index.html I simply called:
{{user.username}} {{user.company}} ...
And so forth.
Now I have an array of objects, I use this method:
app.factory('Cards', function($http) {
var cards = [{id:null, name: null, info: null}];
$http.get('/webservice/cards').success(function(data){
for(var i=0;i<data.length;i++){
cards[i] = data[i];
}
})
return cards;
})
The controller looks the same.
app.controller('SearchCtrl', ['$scope', 'Cards', function ($scope, Cards){
$scope.cards = Cards;
}]);
And I access them with a
<li ng-repeat="card in cards">
{{card.id}} {{card.info}}</li>
My question is, do I really have to have a for loop in the $http.get() method?
No Need for the loop, angular js ng-repeat itself works as an foreach loop.
// Js Code
app.factory('Cards', function($http) {
$scope.cards = [];
$http.get('/webservice/cards').success(function(data){
$scope.cards = data;
}
return $scope.cards;
})
//html
<li ng-repeat="card in cards">
{{card.id}} {{card.info}}</li>
I solved this by using ngResource.
When doing using RESTful APIs this is the way to do it. Instead of using the $http.get() method, I simply used
app.factory('CardService', ['$resource', function($resource) {
return $resource('/webservice/cards',{});
}]);
Using $scope inside of a service is not recommended, that way you have lost the functionality of the service.
In the controller, I then used:
app.controller("CardCtrl", ['$scope', 'CardService', function($scope, CardService){
$scope.cards = CardService.query();
})
Using the other ways caused conflict in the 2-way-binding. First it launched the controller, then checked the service, then the controller, then the service again. When working with an object, this worked great. Working with an array, this way is better.

Categories

Resources