Restangular POST a Mongoose Embedded Document - angularjs

I am having trouble using Restangular to update an existing document (products) within my Mongodb database with a new object reviews. So far I am able to add a review to the front-end with no issues, but I am having trouble posting the review details to my database. Currently when I submit a new review my code creates a new key within my products collection, but does not save the details of the review. How would I push the review to the server? Please let me know if I need to provide any additional details, or clarifications. Any help will be greatly appreciated.
Sample JSON of a product
{"_id":"xxxxx","name":"Product 1","description":"Product 1 Description","price":"1299.99","createdOn":"143767117903", "reviews":[{}]}
After adding a review this what my JSON output of my new review
{"__v":0,"_id":"xxxxxx"}
This is what I am expecting to see within my JSON output
{"__v":0,"_id":"xxxxxx","stars":4,"body":"Test review","author":"example#domain.com","createdOn":143767117903}
Project details
I used the Yeoman angular generator so I have a server and a client directories. I am using MongoDB, MongooseJS,ExpressJS, AngularJS, and NodeJS. As far as I know my server routes for my products are working as I am able to view all products, add a product, view a product (include any reviews associated with the product), and at least add a blank review.
I have a products schema that includes an Embedded Document to a reviews schema.
Products Schema
/**
* Schema for Products
*/
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var productSchema = new Schema({
name: {
type: String,
require: true,
},
description: {
type: String,
require: true,
},
shine: {
type: Number,
require: true,
},
price: {
type: Number,
require: true,
},
rarity: {
type: Number,
require: true,
},
color: {
type: String,
require: true,
},
faces: {
type: Number,
require: true,
},
images: {},
reviews: [{type: mongoose.Schema.Types.ObjectId, ref: 'Review'}],
createdOn: {
type: Date
}
});
var Product = mongoose.model('Product', productSchema);
module.exports = Product;
Reviews schema
/**
* Schema for Product Reviews
*/
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var reviewSchema = new Schema({
stars: {
type: Number
},
review: {
type: String
},
author: {
type: String
},
createdOn: {
type: Date
}
});
var Review = mongoose.model('Review', reviewSchema);
module.exports = Review;
Products Reviews Controller
(function() {
'use strict';
/**
* #ngdoc function
* #name gemStoreApp.controller:ReviewCtrl
* #description
* # ReviewCtrl
* Controller of the gemStoreApp
*/
angular.module('gemStoreApp')
.controller("ReviewCtrl", ['$scope', 'Restangular', 'productsService', function ($scope, Restangular, productsService) {
this.review();
this.addReview = function(product){
this.review.createdOn = Date.now();
var productReview = Restangular.all('/products/' + product._id + '/reviews');
productReview.post(product).then(function(newResource){
});
};
})();
Products Service
(function() {
'use strict';
/**
* #ngdoc service
* #name gemStoreApp.productService
* #description
* # productService
*/
angular.module('gemStoreApp.productService',['ngResource'])
.factory('productsService', function($resource) {
return $resource('/products/:id', {id:'#id'},{
'update': { method: 'PUT'}
});
});
})();

The solution which was provided by a co-worker is to edit the server routes for my product/reviews routed.
updated products review controller
angular.module('gemStoreApp')
.controller('ReviewCtrl', ["$scope, ,'$resource', '$Restangular', 'productsService', function ($scope, $resource, Restangular, productsService) {
this.review = {};
this.addReview = function(product){
this.review.createdOn = Date.now();
var productReview = Restangular.all('/products/' + product._id + '/reviews');
var updatedProduct = product;
productReview.post(this.review).then(function(newResource){
product.reviews.push(newResource);
});
this.review = {};
};
}]);
updated reviews route
...
router.post('/:product/reviews', function (req, res, next) {
var time = moment().formated('MMMM Do YYYY, h:mm:ss a');
var body = req.review;
var review = new Review(req.body);
review.product = req.product
review.save(function (err, review){
if (err) {return next(err);}
req.product.reviews.push(review);
req.products.save(function (err, product) {
if (err) {return next(err);}
res.json(review);
res.status(201).json({
'message': review.review + 'created'
});
});
});
...
I will also post the direct URL to the files on my GitHub project once I commit my changes.

Related

MEAN Stack, MongoDB record creation not working

I am developing a MEAN stack application. I am trying to simply create a record in MongoDB from a form. I have verified in the debugger that the data binding is working between the view and the controller. In the server side controller code, checking the req.body before trying to save the record returns "undefined" (see below in the code). In the Angular controller, I have examined the "results" value in the callback when the announcement.$save function is executed and it shows the heading and details values to be populated as they should. However, the data is not persisted to the database and I get the following error:
{ [ValidationError: Announcement validation failed]
message: 'Announcement validation failed',
name: 'ValidationError',
errors:
{ details:
{ [ValidatorError: Path `details` is required.]
properties: [Object],
message: 'Path `details` is required.',
name: 'ValidatorError',
kind: 'required',
path: 'details',
value: undefined },
heading:
{ [ValidatorError: Path `heading` is required.]
properties: [Object],
message: 'Path `heading` is required.',
name: 'ValidatorError',
kind: 'required',
path: 'heading',
value: undefined } } }
What am I missing? Here is my code:
The form in my html file:
<form ng-submit="AnnouncementsVm.createAnnouncement()">
<fieldset class="form-group">
<label for="heading">Heading:</label>
<textarea class="form-control" id="heading" rows="1"
ng-model="AnnouncementsVm.announcementHeading"></textarea>
</fieldset>
<fieldset class="form-group">
<label for="details">Details:</label>
<textarea class="form-control" id="details" rows="3"
ng-model="AnnouncementsVm.announcementDetails"></textarea>
</fieldset>
<p><input type="submit" value="Submit →"><p>
</form>
The angular route for this page partial is defined as:
$routeProvider.
when('/announcements', {
templateUrl: '/views/partials/announcements.html',
controller: 'Announcements.Controller',
controllerAs: 'AnnouncementsVm'
});
Here is my controller code:
angular.module('app').
controller('Announcements.Controller', AnnouncementsCtrl);
function AnnouncementsCtrl($log, $resource) {
$log.debug('Executing AnnouncementsCtrl');
var vm = this;
var Announcement = $resource('/api/announcements');
Announcement.query( function(results) {
vm.announcements = results;
});
vm.announcements = [];
vm.createAnnouncement = function() {
var announcement = new Announcement({
heading: vm.announcementHeading,
details: vm.announcementDetails
});
announcement.$save( function(result) {
vm.announcements.push(result);
vm.announcementHeading = '';
vm.announcementDetails = '';
});
};
}
The REST API route is defined as:
app.post('/api/announcements', announcementsController.create);
The server side controller (announcements-controller.js):
'use strict';
var Announcement = require('../models/Announcement.js');
module.exports.create = function(req, res) {
var announcement = new Announcement(req.body);
console.log(req.body); // returns "undefined"
announcement.save( function(err, result) {
if (err) console.log(err);
console.log('Save Announcement Result: ' + result);
res.json(result);
});
};
module.exports.list = function(req, res) {
Announcement.find({}, function (err, results) {
if (err) throw err;
res.json(results);
});
};
And finally, I am using this Mongoose model (Announcements.js)
'use strict';
var mongoose = require('mongoose');
var AnnouncementSchema = new mongoose.Schema({
heading: {type: String, required: true},
details: {type: String, required: true},
image: {type: String, required: false}
});
module.exports = mongoose.model('Announcement', AnnouncementSchema);
How is configured your routes in Angular? Are you passing the controller as 'AnnouncementsVm'?
Have you tried to access the values of the ng-models announcementHeading and announcementDetails from the controller?
Try to put
vm.createAnnouncement = function() {
$log.log(vm.announcementHeading);
$log.log(vm.announcementDetails);
});
};
And check if you are getting the correct values
Problem solved. I had not integrated the body-parser for the route so the request wasn't being populated correctly. Here is the updated ExpressJS route:
'use strict';
var bodyParser = require('body-parser');
var path = require('path');
var announcementsController = require('../controllers/announcements-controller.js');
module.exports = function(app) {
// create application/json parser
var jsonParser = bodyParser.json();
// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({ extended: false });
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname, '../../client/views/index.html'));
});
// REST API
app.get('/api/announcements', announcementsController.list);
app.post('/api/announcements', jsonParser, announcementsController.create);
};

AngularJS to Mongoose params on queries

I'm using the mean stack and I can´t figure out how to pass params to mongoose query from the angular controller.
From the mean stack (https://github.com/meanjs/mean) example, we have:
On the server side
an article model
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Article', ArticleSchema);
an article controller with a function to obtain a list of all articles and another function to obtain an article by Id
/**
* List of Articles
*/
exports.list = function(req, res) {
Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
/**
* Article middleware
*/
exports.articleByID = function(req, res, next, id) {
Article.findById(id).populate('user', 'displayName').exec(function(err, article) {
if (err) return next(err);
if (!article) return next(new Error('Failed to load article ' + id));
req.article = article;
next();
});
};
and the articles routes
/**
* Module dependencies.
*/
var users = require('../../app/controllers/users.server.controller'),
articles = require('../../app/controllers/articles.server.controller');
module.exports = function(app) {
// Article Routes
app.route('/articles')
.get(articles.list)
.post(users.requiresLogin, articles.create);
app.route('/articles/:articleId')
.get(articles.read)
.put(users.requiresLogin, articles.hasAuthorization, articles.update)
.delete(users.requiresLogin, articles.hasAuthorization, articles.delete);
// Finish by binding the article middleware
app.param('articleId', articles.articleByID);
};
on the client side
we have an articles module with a routes config file
// Setting up route
angular.module('articles').config(['$stateProvider',
function($stateProvider) {
// Articles state routing
$stateProvider.
state('listArticles', {
url: '/articles',
templateUrl: 'modules/articles/views/list-articles.client.view.html'
}).
state('viewArticle', {
url: '/articles/:articleId',
templateUrl: 'modules/articles/views/view-article.client.view.html'
});
}
]);
an articles controller
angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles',
function($scope, $stateParams, $location, Authentication, Articles) {
$scope.authentication = Authentication;
$scope.find = function() {
$scope.articles = Articles.query();
};
$scope.findOne = function() {
$scope.article = Articles.get({
articleId: $stateParams.articleId
});
};
}
]);
and a list view
<section data-ng-controller="ArticlesController" data-ng-init="find()">
<div class="page-header">
<h1>Articles</h1>
</div>
<div class="list-group">
<a data-ng-repeat="article in articles" data-ng-href="#!/articles/{{article._id}}" class="list-group-item">
<small class="list-group-item-text">
Posted on
<span data-ng-bind="article.created | date:'mediumDate'"></span>
by
<span data-ng-bind="article.user.displayName"></span>
</small>
<h4 class="list-group-item-heading" data-ng-bind="article.title"></h4>
<p class="list-group-item-text" data-ng-bind="article.content"></p>
</a>
</div>
<div class="alert alert-warning text-center" data-ng-if="articles.$resolved && !articles.length">
No articles yet, why don't you create one?
</div>
My question is:
If I want to find all the article of a user, how can I pass a variable param to the find() function in the angular view?
I thought that the Articles.query() in the angular controller works as a mongodb or mongoose command, but I wasn't able to implement it.
Pass an object in query() method and it will be sent to server as query variables. In server use req.query to get those variables:
Client:
$scope.articles = Articles.query({user: 'user_id'});
Server:
Article.find({user: req.query.user}).sort('-created').populate('user', 'displayName').

Filtering all items of specific type (ref document)

I am trying to query all inventory.products that are of inventory.product.type computers. I am not sure how to do this. I have read the documentation and have tried a few but it seems like: db.inventory.products.find({type: { code: {$in: ['computers'] } } }) would be the proper way but never get back any products of the type.
The reason I am building my query in mongo prompt is so I can move it to my service when I have success.
Any advice?
Inventory.Product
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProductSchema = new Schema({
name: {
type: String,
required: 'Name is required'
},
type: {
type: Schema.ObjectId,
ref: 'Inventory.Product.Type',
required: 'Product type is required'
},
});
mongoose.model('Inventory.Product', ProductSchema);
Inventory.Product.Type
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProductTypeSchema = new Schema({
code: {
type: String,
trim: true
},
name: {
type: String,
required: 'Please enter name',
trim: true
},
});
mongoose.model('Inventory.Product.Type', ProductTypeSchema);
The solution was to create a method in my service like so:
service.autoCompleteType = function(query) {
var productList = null;
var products = [];
return Product
.find()
.populate('type')
.exec()
.then(function(products){
productList = products;
})
.then(function(quantities){
for (var i in productList) {
if (productList[i].type.code === query) {
products.push(productList[i]);
}
}
return products;
});
};
This allowed me to query type.code as required and returned everything matching query

Save mongodb data from post form and angular

This is my jade file (basically # stand for id and . for class) and I want to send this to mongodb and save user.activity
textarea#post-form.form-control(
rows="4",
placeholder="Share to world of LinkMe",
ng-model='user.activity'
)
button#post-btn.btn.btn-primary.pull-right(ng-click="post(user)") Link
Here is my code for the controller:
$scope.post = function(user){
auth.post(user).then(function(){
$scope.activity = user.activity;
})
}
And the auth.post function :
post: function(user){
var deferred = $q.defer();
var updatedUser = new UsersResource(user);
updatedUser._id = identity.currentUser._id;
updatedUser.$update().then(
function(){
identity.currentUser.activity = updatedUser.activity;
deferred.resolve();
},
function(response){
deferred.reject(response);
}
);
return deferred.promise;
}
Good thing is I see the change when this happens, but it is not saved in the database.
This is the userSchema:
var userSchema = mongoose.Schema({
username: {type: String, require: '{PATH} is required' , unique: true},
firstName: {type: String, require: '{PATH} is required'},
lastName: {type: String, require: '{PATH} is required'},
profilePic: {type: String, default: 'imgs/default.jpg'},
activity: String,
salt: String,
hashPass: String,
roles: [String]
});
I really appreciate help :)
P.P. I have this on server now :
app.put('/collections/:collectionName/:id', function(req, res, next) {
req.collection.updateById(req.params.id, {$set:req.body}, {safe:true, multi:false}, function(e, result){
if (e) return next(e)
res.send((result===1)?{msg:'success'}:{msg:'error'})
})
})
Problem now is that I have model of User from userSchema but I didn't declare any collections. What should I do ?
You'll need a REST interface on the server side to work with Mongo, take a look at this:
http://docs.mongodb.org/ecosystem/tools/http-interfaces/#HttpInterface-RESTInterfaces
Let's say you are using Node.js with Express and Mongoose, you will have to set up a REST api along the lines of:
var app = express();
var mongoose = require('mongoose');
var database_url = "mongodb://localhost:27017/Database";
var listen_port = 8080; // example port
var collections = ["collection1",...];
mongoose.connect(database_url);
var Model1 = mongoose.model('collection1', userSchema); // <--- your user schema
// example of a getter by id
app.get('/api/get/:id', function(req, res) {
Model1.find({ _id: req.params.id }, function(err, post){
res.send(post);
});
});
app.listen(listen_port);
and set up dependencies in your package.json file, like for instance
"dependencies" : {
"express" : "~3.4.4",
"mongoose" : "~3.6.2"
}
And then make use of Angular $http service in order to perform async calls to the API.
https://docs.angularjs.org/api/ng/service/$http

AngularJS-MongoLab $resource update error

I am connecting AngularJS with MongoLab and trying to update the "users" collection.
AngularJS resource service:
angular.module("myApp.services", ["ngResource"])
.constant({
DB_URL: "https://api.mongolab.com/api/1/databases/mydb/collections/users/:id",
API_KEY: "[SOME_RANDOM_KEY]"
})
.factory("UsersService", ["$resource", "DB_URL", "API_KEY", function($resource, DB_URL, API_KEY) {
return $resource(DB_URL, { apiKey: API_KEY, id: "#_id.$oid" }, { update: { method: "PUT" }});
}]);
This is how I am trying to update my users collection:
angular.module("myApp", ["myApp.services"])
.controller("AppController", ["$scope", "UsersService", function($scope, UsersService) {
$scope.users = [];
$scope.getAllUsers = function() {
$scope.users = UsersService.query();
};
$scope.updateUser = function(user) { // user: firstName, lastName, email
//delete user._id;
UsersService.update({}, user, function() {
$scope.users = UsersService.query();
console.log("Users updated successfully");
}, function() {
console.log("Some problems updating the user", arguments);
});
};
}]);
When I try to update the user information, it throws an exception stating:
{ "message" : "cannot change _id of a document old:{ _id: ObjectId('[SOME_RANDOM_KEY]'), firstName: \"Anup\", lastName: \"Vasudeva\", email: \"anup.vasudeva#emal.com\" } new:{ _id: {}, firstName: \"Anup\", lastName: \"Vasudeva\", email: \"anup.vasudeva#email.com\" }"
I am new to MongoDB, so I don't understand why it is creating an empty _id object for the new user instance?
Try changing your $scope.updateUser function to the following:
$scope.updateUser = function (user) {
var userId = user._id.$oid;
user._id = undefined;
UsersService.update({id: userId}, user, function () {
$scope.users = UsersService.query();
console.log("Users updated successfully");
}, function () {
console.log("Some problems updating the user", arguments);
});
};
The update/replacement object ('user'), should not contain the _id property when being passed to UsersService.update(). Mongo will not replace the _id value, so the id will stay the same after the update.
One other thing that's changed in this function is that we're passing the user id as the first parameter of the UsersService.update() function so that mongo knows which document to update.

Resources