AngularJS to Mongoose params on queries - angularjs

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').

Related

How to post file and data with AngularJS with MEAN stack

I went through hundreds of pages for several days without success and here is my problem.
I use the MEAN stack and at this point I have a simple form that works very well to save a "name" field to a MongoDB collection. Now, I would like, client-side, add an image upload and, on form submit, store the image on my server and finally save the "name" field and the image path to the MongoDB collection.
AngularJS side, I tried using ng-file-upload with multer server-side. I have done well to operate for the upload of the file but only that. But after hundreds of tests, I despair. Here is an extract of my original code without file upload.
Server side
sections.server.model
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var SectionSchema = new Schema({
name: {
type: String,
default: '',
trim: true,
required: true,
unique: true
},
image: {
type: String,
default: ''
}
});
mongoose.model('Section', SectionSchema);
sections.server.controller
exports.create = function (req, res) {
var section = new Section(req.body);
section.save(function (err) {
if (err) {
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
res.json(section);
}
});
};
sections.server.routes
var sections = require('../../app/controllers/sections.server.controller');
module.exports = function (app) {
app.route('/api/sections')
.post(sections.create);
};
Client side
sections.client.module
'use strict';
var sections = angular.module('sections', []);
sections.client.controller
'use strict';
angular.module('sections')
.controller('SectionsController',
['$scope', '$routeParams', '$location', 'Sections'
function ($scope, $routeParams, $location, Sections) {
$scope.create = function () {
var section = new Sections({
name: this.name
});
section.$save(function (response) {
$location.path('sections/' + response._id);
}, function (errorResponse) {
$scope.error = errorResponse.data.message;
});
};
}]);
sections.client.routes
angular.module('sections').config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/sections', {
controller: 'SectionsController',
templateUrl: 'sections/views/list-sections.client.view.html'
})
.when('/sections/create', {
controller: 'SectionsController',
templateUrl: 'sections/views/create-section.client.view.html'
})
.otherwise({
redirectTo: '/'
});
}]);
sections.client.service
'use strict';
angular.module('sections').factory('Sections', ['$resource', function ($resource) {
return $resource('api/sections/:sectionId', {
sectionId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}]);
create-section.client.view
<section>
<h1>New Article</h1>
<form data-ng-submit="create()" novalidate>
<div>
<label for="name">Nom du rayon</label>
<div>
<input type="text" data-ng-model="name" id="name" placeholder="Name" required>
</div>
</div>
<div>
<input type="submit">
</div>
<div data-ng-show="error"><strong data-ng-bind="error"></strong></div>
</form>
</section>
Now, from this can anyone help me to add the image upload in the form and then save the field name and the image path in MongoDB.
Note that I want to reuse the upload mecanism in other forms of my app.
I had the idea of switching a generic middleware function in the road-side server wich call multer and return the image path to my sections.create function for MongoDB storing, something like that :
module.exports = function (app) {
app.route('/api/sections')
.post(uploads.upload, sections.create);
But I've never managed to pass the file in the POST from AngularJS request.
Thank you so much for all your ideas, your help and possibly an example of code that works.

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);
};

PUT request overwrites array values with duplicate values

I am making a small polls app with Yeoman's angular-fullstack generator. I have got to the stage where I am implementing user selection to increment a polls count, which then updates my MongoDB database via a PUT request.
Question is set up ready for the user to vote:
The object has individual IDs:
Then, a user goes to vote on an answer. Looks fine on the client:
But it's not updating correctly in the server. All items in the array change to the first item, with identical IDs:
When the client is refreshed, it loads the data from the server and obviously this is not what I want:
What am I doing wrong?
Here is the view:
<form ng-submit="submitForm()">
<div ng-repeat="answer in poll.answers">
<label><input type="radio" name="option" ng-model="radioData.index" value="{{$index}}"/>
{{ answer.value }} - {{ answer.votes }} Votes
</label>
</div>
<button class="btn btn-success" type="submit">Vote!</button>
</form>
Here is the controller:
'use strict';
angular.module('angFullstackCssApp')
.controller('ViewCtrl', function ($scope, $routeParams, $http) {
$http.get('/api/polls/' + $routeParams._id).success(function (poll) {
console.log(poll);
$scope.poll = poll;
$scope.radioData = {
index: 0
};
$scope.submitForm = function () {
console.log($scope.radioData.index);
$scope.poll.answers[$scope.radioData.index].votes += 1;
console.log('scope poll answers:- ', $scope.poll.answers);
console.log('scope poll answers[index]:- ', $scope.poll.answers[$scope.radioData.index]);
console.log('votes:- ', $scope.poll.answers[$scope.radioData.index].votes);
// Change database entry here
$http.put('/api/polls/' + $routeParams._id, {answers: $scope.poll.answers}).success(function () {
console.log('success');
});
};
});
});
Here is the relevant server-side code, all left from the default of my route and endpoint setting up:
router.put('/:id', controller.update);
// Updates an existing poll in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Poll.findById(req.params.id, function (err, poll) {
if (err) { return handleError(res, err); }
if(!poll) { return res.status(404).send('Not Found'); }
var updated = _.merge(poll, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.status(200).json(poll);
});
});
};
And the schema:
var PollSchema = new Schema({
creator: String,
title: String,
answers: [{
value: String,
votes: Number
}]
}, { versionKey: false });

AngularJS and UI-Router: keep controller loaded

I am building a web application for our customer support. One of the needs is to be able to keep multiple tickets opened at the same time.
I was able to do the first part easily using a tabulation system and UI-Router.
However, with my current implementation, each time I change active tab, the previously-current tab is unloaded, and the now-current tab is loaded (because it was unloaded with a previous tab change).
This is not at all the expected behavior. I've already spent a couple of days trying to find a way to achieve this, without any luck.
The closest thing I was able to do is to use the multiple views system from UI-Router, but I need multiple instance of the same view to keep in memory (if multiple tickets are opened, they all are on the same view, with the same controller, but a different scope)
Here's my current implementation:
supportApp.js:
var app = angular.module("supportApp", ["ui.router", "ui.bootstrap"]);
app.config(function($stateProvider, $urlRouterProvider, $httpProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.decorator('d', function(state, parent){
state.templateUrl = generateTemplateUrl(state.self.templateUrl);
return state;
})
.state("main", {
abtract: true,
templateUrl: "main.html",
controller: "mainController"
})
.state("main.inbox", {
url: "/",
templateUrl: "inbox.html",
controller: "inboxController"
})
.state('main.viewTicket', {
url: '/ticket/{id:int}',
templateUrl: "viewTicket.html",
controller: "ticketController"
})
;
});
mainController.js: (handles other stuff, minimal code here)
app.controller("mainController", function($rootScope, $http, $scope, $state, $interval){
// Tabs system
$scope.tabs = [
{ heading: "Tickets", route:"main.inbox", active:false, params:{} }
];
var addTabDefault = {
heading: '',
route: null,
active: false,
params: null,
closeable: false
};
$rootScope.addTab = function(options){
if(!options.hasOwnProperty('route') || !options.route)
{
throw "Route is required";
}
var tabAlreadyAdded = false;
for(var i in $scope.tabs)
{
var tab = $scope.tabs[i];
if(tab.route == options.route && angular.equals(tab.params, options.params))
{
tabAlreadyAdded = true;
break;
}
}
if(!tabAlreadyAdded)
{
$scope.tabs.push($.extend({}, addTabDefault, options));
}
if(options.hasOwnProperty('active') && options.active === true)
{
$state.go(options.route, options.hasOwnProperty('params')?options.params:null);
}
};
$scope.removeTab = function($event, tab){
$event.preventDefault();
if($scope.active(tab.route, tab.params))
{
$scope.go($scope.tabs[0].route, $scope.tabs[0].params);
}
$scope.tabs.splice($scope.tabs.indexOf(tab), 1);
};
$scope.go = function(route, params){
$state.go(route, params);
};
$scope.active = function(route, params){
return $state.is(route, params);
};
$scope.$on("$stateChangeSuccess", function() {
$scope.tabs.forEach(function(tab) {
tab.active = $scope.active(tab.route, tab.params);
});
});
});
main.html:
<div class="container-fluid" id="sav-container">
<div class="row-fluid">
<div class="col-lg-2">
<form role="form" id="searchForm" action="#">
<div class="form-group has-feedback">
<input class="form-control" type="search" />
<span class="glyphicon glyphicon-search form-control-feedback"></span>
</div>
</form>
</div>
<div class="col-lg-10" id="support_main_menu">
<ul class="nav nav-tabs">
<li ng-repeat="t in tabs" ng-click="go(t.route, t.params)" ng-class="{active: t.active, closeable: t.closeable}" style="max-width: calc((100% - 128px) / {{tabs.length}});">
<a href class="nav-tab-text">
<button ng-show="t.closeable" ng-click="removeTab($event, t)" class="close" type="button">×</button>
<span>{{t.heading}}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="row-fluid">
<div class="tab-content" ui-view></div>
</div>
</div>
It seems to me that what I ask is pretty standard, but I sadly couldn't find any usefull thing on the Internet
The basic idea is to store state (i.e. list of tickets) in a service as opposed to a controller. Services hang around for the life of the application. There are some articles on this. I'm still developing my approach but here is an example:
var RefereeRepository = function(resource)
{
this.resource = resource; // angular-resource
this.items = []; // cache of items i.e. tickets
this.findAll = function(reload)
{
if (!reload) return this.items;
return this.items = this.resource.findAll(); // Kicks off actual json request
};
this.findx = function(id)
{
return this.resource.find({ id: id }); // actual json query
};
this.find = function(id) // Uses local cache
{
var itemx = {};
// Needs refining
this.items.every(function(item) {
if (item.id !== id) return true;
itemx = item;
return false;
});
return itemx;
};
this.update = function(item)
{
return this.resource.update(item);
};
};
refereeComponent.factory('refereeRepository', ['$resource',
function($resource)
{
var resource =
$resource('/app_dev.php/referees/:id', { id: '#id' }, {
update: {method: 'PUT'},
findAll: {
method: 'GET' ,
isArray:true,
transformResponse: function(data)
{
var items = angular.fromJson(data);
var referees = [];
items.forEach(function(item) {
var referee = new Referee(item); // Convert json to my object
referees.push(referee);
});
return referees;
}
},
find: {
method: 'GET',
transformResponse: function(data)
{
var item = angular.fromJson(data);
return new Referee(item);
}
}
});
var refereeRepository = new RefereeRepository(resource);
// Load items when service is created
refereeRepository.findAll(true);
return refereeRepository;
}]);
So basically we made a refereeRepository service that queries the web server for a list of referees and then caches the result. The controller would then use the cache.
refereeComponent.controller('RefereeListController',
['$scope', 'refereeRepository',
function($scope, refereeRepository)
{
$scope.referees = refereeRepository.findAll();
}
]);

Creating custom service in Angular using MeanJS

Still very new to MeanJS and Angular, but am trying to get a repeater to use a custom node service that i created
Here is the Angular Template
<section data-ng-controller="AppController">
<section data-ng-controller="GroupsController" data-ng-init="findMyItems()">
<div class="page-header">
<h1>My Groups</h1>
</div>
<div class="list-group">
<a data-ng-repeat="group in groups" data-ng-href="#!/groups/{{group._id}}" class="list-group-item">
<small class="pull-right" data-ng-bind="group.shortId"></small>
<h4 class="list-group-item-heading" data-ng-bind="group.name"></h4>
<small class="list-group-item-text">
Posted on
<span data-ng-bind="group.created | date:'medium'"></span>
by
<span data-ng-bind="group.user.displayName"></span>
</small>
</a>
</div>
<div class="alert alert-warning text-center" data-ng-hide="!groups.$resolved || groups.length">
No Groups yet, why don't you create one?
</div>
</section>
</section>
Here is an array of JSON objects returned from localhost:3000/users/me/groups
[
{
_id: "5407dd31594e810000af4fa0",
user: "5407c78f9ef3025bbf0440f7",
description: "Activating....",
__v: 0,
projects: [ ],
created: "2014-09-04T03:32:01.825Z",
shortId: "bkXtE746M",
name: "Wonder Twins"
},
{
_id: "5407dc49a34a610000af6896",
user: "5407c78f9ef3025bbf0440f7",
description: "Loved watching this one",
__v: 0,
projects: [ ],
created: "2014-09-04T03:28:09.480Z",
shortId: "WJejxZorTz",
name: "Fantastic Four"
},
{
_id: "5407d71839c7de000008cf6b",
user: "5407c78f9ef3025bbf0440f7",
description: "Great group",
__v: 0,
projects: [ ],
created: "2014-09-04T03:06:00.098Z",
shortId: "ZJfKDyN6f",
name: "Leaders of the New School"
}
]
Controller
'use strict';
// Groups controller
angular.module('groups').controller('GroupsController', ['$scope', '$stateParams', '$location', 'Authentication', 'Groups', 'GroupsAPI',
function($scope, $stateParams, $location, Authentication, Groups, GroupsAPI ) {
$scope.authentication = Authentication;
$scope.findMyItems = function() {
GroupsAPI.getGroupsByCurrentUser()
.success(function (groups) {
$scope.groups = groups;
})
.error(function (error) {
$scope.status = 'Unable to load group data: ' + error.message;
});
};
}
]);
I'm not exactly sure what the service is doing in MeanJS
'use strict';
//Groups service used to communicate Groups REST endpoints
angular.module('groups').factory('Groups', ['$resource',
function($resource) {
return $resource('groups/:groupId', { groupId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
What I'd like to do is something like to do is something like bellow, but not sure if there is a better way
'use strict';
//Groups service used to communicate Groups REST endpoints
angular.module('groups').factory('Groups', ['$resource',
function($resource) {
return $resource('groups/:groupId', { groupId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
angular.module('groups')
.factory('GroupsAPI', ['$http', function($http) {
var GroupsAPI = {};
GroupsAPI.getGroupsByCurrentUser = function () {
return $http.get('users/me/groups');
};
return GroupsAPI;
}]);
Is there a better way of doing this the MeanJS way?
It's been a long time since you posted this, so you likely figured out a solution already, but for the sake of future readers I'll toss an answer in here.
If I'm understanding what you're trying to do, it looks like you are trying to create a custom factory that functions similar to the existing Groups factory (the one you mentioned you didn't know what it was doing). That's what I'll be answering...
To begin with, you'll want to read the Angular documentation on $resource: https://docs.angularjs.org/api/ngResource/service/$resource. This is what makes the Groups factory work.
To summarize, Angular's $resource allows you to make AJAX requests very easily, by allowing you to create a variable in your controller which has access to REST functions. So basically, you would do something like this:
// Groups controller
angular.module('groups').controller('GroupsController', ['$scope', '$stateParams', '$location', 'Authentication', 'Groups', 'GroupsAPI',
function($scope, $stateParams, $location, Authentication, Groups, GroupsAPI ) {
$scope.authentication = Authentication;
// Since you've added 'Groups' as a dependency, you can now use this "resource" in a new variable.
var groups = new Groups({
// Set any object data you need here, or leave empty.
});
// Now that you have a new instance of your 'Groups' resource (i.e. 'groups'), you can use standard REST calls.
groups.get({
user: currentUser
}, function(results) {
// Do something with results here, e.g.
if(results) {
$scope.groups = results;
} else {
$scope.status = 'Unable to load group data.';
}
});
]);
Note: I haven't tested this code, but this is the idea.

Resources