PUT request overwrites array values with duplicate values - angularjs

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

Related

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

AngularJS show 2 models in one controller

I have 2 models(Project and Task) to show in my home.html, and I want my them to be displayed like this: http://plnkr.co/edit/ItNvBNBIrLxqwRrhye5p, where both scope of data is shown and filtered based on the same color.
I use an Angular controller (projectCtrl.js) to control data on my web page(home.html), and use an Angular service (projectService.js) to grab data from my api file (api.js) written with express framework and mongoose.
But my code doesn't show anything, so I have no idea what's wrong.
home.html:
<div class="row" ng-if="main.loggedIn">
<div ng-controller="ProjectController">
<div class="panel col-md-8">
<!-- Project heading setup -->
<div class="panel-group" ng-repeat="eachProject in project.projects | reverse track by $index">
<div class="panel panel-info">
<div class="panel-heading" data-toggle="collapse" ng-click="true" data-target="#projectDetails{{$index}}" href="#projectDetails{{$index}}">
<h4>{{eachProject.title}}: {{eachProject.short_description}}</h4>
</div>
<div class="panel-collapse collapse out" id="projectDetails{{$index}}">
<p class="panel-body">
<!-- Project detail table, where project data displays-->
<table class="table table-responsive table-bordered table-hover">
<tr>
<th>Description: </th>
<td>{{eachProject.description}}</td>
</tr>
</table>
<!-- Task heading setup -->
<div class="panel-group" ng-repeat="eachTask in task.tasks | filter: { projectID: eachProject.id }">
<div class="panel panel-success">
<div class="panel-heading" data-toggle="collapse" ng-click="true" data-target="#taskDetails{{$index}}" href="#taskDetails{{$index}}">
<h5>{{eachTask.title}}</h5>
</div>
<div class="panel-collapse collapse out" id="taskDetails{{$index}}">
<p class="panel-body">
<!-- Task detail table, where tasks data displays -->
<table class="table table-responsive table-bordered table-hover">
<tr>
<th>Description: </th>
<td>{{eachTask.description}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
ProjectCtrl.js
angular.module('projectCtrl', ['projectService'])
.controller('ProjectController', function(Project, Task, socketio) {
var vm = this;
Project.all()
.success(function(data) {
vm.projects = data;
})
vm.createProject = function() {
// Wrong due date prevention
var start = new Date(vm.projectData.start_date);
var due = new Date(vm.projectData.due_date);
if (start > due) {
alert("Due date can't be earlier than start date, please decide a new due date.");
return;
}
// Create project
vm.message = '';
Project.create(vm.projectData)
.success(function(data) {
// Clear up the project
vm.projectData = '';
vm.message = data.message;
$('#createProject').modal('hide');
})
}
Task.all()
.success(function(data) {
vm.tasks = data;
})
vm.createTask = function() {
// Wrong due date prevention
var start = new Date(vm.taskData.taskStart_date);
var due = new Date(vm.taskData.taskDue_date);
if (start > due) {
alert("Due date can't be earlier than start date, please decide a new due date.");
return;
}
// Create task
vm.message = '';
Task.create(vm.taskData)
.success(function(data) {
// Clear up the task
vm.taskData = '';
vm.message = data.message;
$('#createTask').modal('hide');
})
}
socketio.on('project', 'task', function(data) {
vm.projects.push(data);
vm.tasks.push(data);
})
})
.controller('AllProjectsController', function(projects, socketio) {
var vm = this;
vm.projects = projects.data;
socketio.on('project', function(data) {
vm.projects.push(data);
})
})
projectService.js
angular.module('projectService', [])
.factory('Project', function($http) {
var projectFactory = {};
projectFactory.create = function(projectData) {
return $http.post('/api', projectData);
}
projectFactory.allProjects = function() {
return $http.get('/api/all_projects');
}
projectFactory.all = function() {
return $http.get('/api');
}
projectFactory.deleteProject = function(id) {
return $http.post('/api/deleteProject', {id: id});
}
return projectFactory;
})
.factory('Task', function($http) {
var taskFactory = {};
taskFactory.create = function(taskData) {
return $http.post('/api', taskData);
}
taskFactory.allTasks = function() {
return $http.get('/api/all_tasks');
}
taskFactory.all = function() {
return $http.get('/api');
}
taskFactory.deleteTask = function(id) {
return $http.post('/api/deleteTask', {projectID: id});
}
return taskFactory;
})
.factory('socketio', function($rootScope) {
var socket = io.connect();
return {
on: function(eventName, callback) {
socket.on(eventName, function() {
var args = arguments;
$rootScope.$apply(function() {
callback.apply(socket, args);
})
})
},
emit: function(eventName, data, callback) {
socket.emit(eventName, data, function() {
var args = arguments;
$rootScope.apply(function() {
if(callback) {
callback.apply(socket, args);
}
})
})
}
}
})
api.js
var User = require('../models/user');
var Project = require('../models/project');
var config = require('../../config');
var secretKey = config.secretKey;
var jsonwebtoken = require('jsonwebtoken');
var fields = '...'; // a lot of fields, deleted them just to make it short
// Create tokens for users with jsonwebtoken
function createToken(user) {
var token = jsonwebtoken.sign({
id: user._id,
firstname: user.firstname,
lastname: user.lastname,
username: user.username
}, secretKey, {
expirtesInMinute: 1440
});
return token;
}
module.exports = function(app, express, io) {
var api = express.Router();
api.get('/all_projects', function(req, res) {
Project.find({}, function(err, projects) {
if (err) {
res.send(err);
return;
}
res.json(projects);
})
})
// login api
api.post('/login', function(req, res) {
User.findOne({
username: req.body.username
}).select(fields).exec(function(err, user) {
if(err) {
throw err;
}
if (!user) {
res.send({ message: "User doesn't exist"});
} else if(user){
var validPassword = user.comparePassword(req.body.password);
if (!validPassword) {
res.send({ message: "Invalid Password"});
} else {
var token = createToken(user);
res.json({
success: true,
message: "Login Successfully !",
token: token
});
}
}
});
});
//middleware
api.use(function(req, res, next) {
console.log("Somebody just logged in!");
var token = req.body.token || req.param('token') || req.headers['x-access-token'];
if (token) {
jsonwebtoken.verify(token, secretKey, function(err, decoded) {
if (err) {
res.status(403).send({success: false, message: "Failed to authenticate user."});
} else {
req.decoded = decoded;
next();
}
});
} else {
res.status(403).send({ success: false, message: "No Token Provided." });
}
});
//api for projects handling
api.route('/')
.post(function(req, res) {
var project = new Project({
creatorID: req.decoded.id,
creator: req.decoded.firstname + " " + req.decoded.lastname,
creator_dept: req.decoded.department,
title: req.body.title,
short_description: req.body.short_description,
description: req.body.description,
priority: req.body.priority,
status: calcStatus(),
assign_dept: req.body.assign_dept,
estimate_cost: req.body.estimate_cost,
actual_cost: req.body.actual_cost,
last_modified_date: req.body.last_modified_date,
due_date: req.body.due_date,
start_date: req.body.start_date,
complete_date: req.body.complete_date,
});
project.save(function(err, newProject) {
if (err) {
res.send(err);
return;
}
io.emit('project', newProject);
res.json({
message: "New Project Created!"
});
});
})
.get(function(req, res) {
Project.find( {creatorID: req.decoded.id}, function(err, project) {
if (err) {
res.send(err);
return;
}
res.json(project);
});
});
//api for tasks handling
api.route('/')
.post(function(req, res) {
var task = new Task({
creatorID: req.decoded.id,
creator: req.decoded.firstname + " " + req.decoded.lastname,
projectID: req.body.taskProjectID,
title: req.body.taskTitle,
description: req.body.taskDescription,
status: calcStatus(),
assigneeName: req.body.assigneeName,
assigneeID: req.body.assigneeID,
assignee_dept: req.body.assignee_dept,
estimate_cost: req.body.taskEstimate_cost,
actual_cost: req.body.TaskActual_cost,
last_modified_date: req.body.taskLast_modified_date,
due_date: req.body.taskDue_date,
start_date: req.body.taskStart_date,
complete_date: req.body.taskComplete_date,
});
task.save(function(err, newTask) {
if (err) {
res.send(err);
return;
}
io.emit('tasks', newTask);
res.json({
message: "New Task Created!"
});
});
})
.get(function(req, res) {
Task.find( {projectID: req.decoded.id}, function(err, task) {
if (err) {
res.send(err);
return;
}
res.json(task);
});
});
// api for angular
api.get('/me', function(req, res) {
res.json(req.decoded);
});
return api;
}
Thanks for the help.
You reference project.projects here:
<div class="panel-group" ng-repeat="eachProject in project.projects | reverse track by $index">
But project is never in ProjectController... I think you may want to set project to refer to your ProjectController, try this:
<div ng-controller="ProjectController as project">
EDIT
A few other problems. You mentioned you wanted to show all Tasks as well, in ProjectController you populate projects and tasks like so:
Project.all()
.success(function(data) {
vm.projects = data;
})
....
Task.all()
.success(function(data) {
vm.tasks = data;
})
That's good, except the definitions for Project.all and Task.all are referencing the same data source:
projectFactory.all = function() {
return $http.get('/api');
}
taskFactory.all = function() {
return $http.get('/api');
}
They both reference /api. How can they both expect different data from the same route? They should likely be two distinct routes.
In addition, your definition in api.js for this end point requires an ID to be passed to get a single project:
.get(function(req, res) {
Task.find( {projectID: req.decoded.id}, function(err, task) {
if (err) {
res.send(err);
return;
}
res.json(task);
});
});
.get(function(req, res) {
// The request must contain a creatorId, otherwise you're not going to find your project
Project.find( {creatorID: req.decoded.id}, function(err, project) {
if (err) {
res.send(err);
return;
}
res.json(project);
});
});
Perhaps your projectService should be pointing to the /all_projects end point instead for .all. So to sum up:
API end points for Project/Task should be different
Be sure to pass the project ID/task if necessary, otherwise you're not going to get the data you're expecting.
Set breakpoints and log output to trace your code path to see where other mistakes may be.

error performing a get request

I am trying to perform a get request, in the post request everything is OK, I can see that in the post request all I do it's been save, once I refresh the page I am printing in the console the items saved by the post request, but those items aren't been return with the get I am doing.
here is where everything begins, I have a list of items here with the option to checked or unchecked the items in the list
<ion-item ng-repeat="sport in sports" ng-click="toggleSportSelection(sport)">
{{:: sport.name}}
</ion-item>
all the items are checked = true by default, so what I am saving, are the items with checked = false, the items checked = true you can see them here
<div ng-show="sport.checked" ng-repeat="sport in sports">
{{sport.name}}
</div>
this is what I have in the controller
.controller('SportsController', function($scope, SportsFactory) {
SportsFactory.getSportChecked(customer).then(function(sportChecked) {
_.each(sports, function(sport) {
var sportIds = _.pluck(sport, 'id'),
intersectedSports = _.intersection(sport.id, sportChecked),
checkedSportObjects = _.filter(sport, function(sportObj) {
return _.includes(intersectedSports, sportObj);
});
_.each(checkedSportObjects, function(sport) {
$scope.sports.push(sport);
});
});
}, function(err) {
console.log(err);
});
$scope.toggleSportSelection = function(sport) {
var params = {};
params.user = $scope.customer.customer;
params.sport = sport.id;
sport.checked = !sport.checked;
SportsFactory.setSportChecked(params);
};
// this is what puts the sports on checked = true
if (sports.length) {
$scope.sports = _.map(sports, function(sport) {
sport.checked = true;
return sport;
});
}
and this is the service / factory
.factory('SportsFactory', function($http, $q, AuthFactory,
LocalForageFactory, CONSTANT_VARS) {
return {
getSportChecked: function(customer) {
var defer = $q.defer(),
user,
rejection = function(err) {
console.log(err);
defer.reject(err);
};
LocalForageFactory.retrieve(CONSTANT_VARS.LOCALFORAGE_SPORTS_CHECKED)
.then(function(sportChecked) {
user = customer.customer;
if (!_.isNull(sportChecked)) {
defer.resolve(sportChecked);
}else {
$http.get(CONSTANT_VARS.BACKEND_URL + '/sports/getChecked/' + user)
.success(function(sportChecked) {
LocalForageFactory.set(CONSTANT_VARS.LOCALFORAGE_SPORTS_CHECKED, sportChecked);
defer.resolve(sportChecked);
})
.error(rejection);
}
}, rejection);
return defer.promise;
}
});
I am working along with NodeJS, so if you want the code I have in that part just let me know, so far I think that the issue I have is in the front end part, in the controller.

AngularFire $remove item from Array using a variable in Firebase reference does not work

I've been struggling with the following problem:
I'm trying to delete a 'Post' item from a Firebase Array with the $remove AngularFire method which I have implemented in a Angular Service (Factory). This Post is a child of 'Event', so in order to delete it I have to pass this Service a argument with the relevant Event of which I want to delete the post.
This is my controller:
app.controller('EventSignupController', function ($scope, $routeParams, EventService, AuthService) {
// Load the selected event with firebase through the eventservice
$scope.selectedEvent = EventService.events.get($routeParams.eventId);
// get user settings
$scope.user = AuthService.user;
$scope.signedIn = AuthService.signedIn;
// Message functionality
$scope.posts = EventService.posts.all($scope.selectedEvent.$id);
$scope.post = {
message: ''
};
$scope.addPost = function (){
$scope.post.creator = $scope.user.profile.username;
$scope.post.creatorUID = $scope.user.uid;
EventService.posts.createPost($scope.selectedEvent.$id, $scope.post);
};
$scope.deletePost = function(post){
EventService.posts.deletePost($scope.selectedEvent.$id, post);
// workaround for eventService bug:
// $scope.posts.$remove(post);
};
});
And this is my Service (Factory):
app.factory('EventService', function ($firebase, FIREBASE_URL) {
var ref = new Firebase(FIREBASE_URL);
var events = $firebase(ref.child('events')).$asArray();
var EventService = {
events: {
all: events,
create: function (event) {
return events.$add(event);
},
get: function (eventId) {
return $firebase(ref.child('events').child(eventId)).$asObject();
},
delete: function (event) {
return events.$remove(event);
}
},
posts: {
all: function(eventId){
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts;
},
createPost: function (eventId, post) {
// this does work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$add(post);
},
deletePost: function (eventId, post) {
// this does not work
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
return posts.$remove(post);
}
}
};
return EventService;
});
When I try to delete the link tag just freezes and no error logging appears in the console. While if I call $remove on my $scope.posts directly in my controller it magically works.. Furthermore my Post is not removed from my Firebase DB.
Another weird thing is that 'CreatePost' works perfectly fine using the same construction.
My view:
<div class="col-xs-8 col-xs-offset-2 well">
<form ng-submit="addPost()" ng-show="signedIn()">
<input type="text" ng-model="post.message" />
<button type="submit" class="btn btn-primary btn-sm">Add Post</button>
</form>
<br>
<div class="post row" ng-repeat="post in posts">
<div>
<div class="info">
{{ post.message }}
</div>
<div>
<span>submitted by {{ post.creator }}</span>
delete
</div>
<br>
</div>
</div>
</div>
P.s. I'm not too sure that my 'Service' is implemented in the best possible way.. I couldn't find another solution for doing multiple firebase calls
var posts = $firebase(ref.child('events').child(eventId).child('posts')).$asArray();
within the Post part of my EventService, because it depends on eventId in each CRUD operation. Any ideas would be very welcome :)
The easiest way for me was to use this:
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
ref.child(postId).remove(function(error){
if (error) {
console.log("Error:", error);
} else {
console.log("Removed successfully!");
}
});
The only way I'm able to remove the item is using a loop on the array we get from firebase.
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
var arr_ref=$firebaseArray(ref);
for(var i=0;i<arr_ref.length;i++){
if(key==arr_ref[i].$id){
console.log(arr_ref[i]);
arr_ref.$remove(i);
}
}

Document sub-arrays stored as duplicate values of the first entry in Mongo

I'm using angular-fullstack and Mongolabs when attempting to update any document sub-arrays the values that are stored in Mongolabs appear simply as duplicate values of the first element in the array.
The Mongoose schema is set as
techspecs: [{label: String, value: String, _id: false}],
When updating the payload appears correct and contains different values e.g.
techspecs: [{label: "Dimensions", value: "1200x800mm"}, {label:"Capacity", value: "532 l"}]
However in Mongolabs the values are stored as
"techspecs": [
{
"label": "Dimensions",
"value": "1200x800mm"
},
{
"label": "Dimensions",
"value": "1200x800mm"
}
],
If I have more value pairs it just keeps storing multiple duplicates of the first element.
The update is done using $resource through a factory service
angular.module('prototypeMeanApp')
.factory('productAPI', ['$resource', function($resource) {
return $resource('api/products/:id', {id: '#_id'}, {
'update': { method:'PUT'},
'create': { method:'POST'},
'query' : {method:'GET', isArray:true}
});
}
]);
In the controller it's being updated via the following function
$scope.updateProduct = function(form) {
$scope.submitted = true;
var pId = $scope.product._id;
if(form.$valid) {
productAPI.update({id: pId}, {
_id: pId,
categoryid: $scope.currentcat[0]._id,
brand: $scope.product.brand,
model: $scope.product.model,
heading: $scope.product.heading,
description: $scope.product.description,
price: $scope.product.price,
techspecs: $scope.product.techspecs,
lastmodified: Date.now()
}).$promise.then(function() {
console.log('Product updated');
}, function(err) {
err = err.data;
$scope.errors = {};
angular.forEach(err.errors, function(error, field) {
form[field].$setValidity('mongoose', false);
$scope.errors[field] = error.message;
});
});
And in the view
<div class="form-group" data-ng-repeat="techspec in product.techspecs">
<label class="control-label hidden-xs col-sm-3">Tech Spec #{{$index+1}}</label>
<div class="col-sm-4 col-md-3">
<div class="controls">
<input type="text" data-ng-model="techspec.label" class="form-control" placeholder="Label" />
</div>
</div>
<div class="col-sm-4 col-md-3">
<div class="controls">
<input data-ng-model="techspec.value" type="text" class="form-control" placeholder="Value" />
</div>
</div>
</div>
I ended up a simple test with mean.io to verify that this possible to do and Mean.io successfully stored the document sub-arrays into Mongo. After this I compared the source and implementation methods for angular-fullstack and mean.io to see what was different.
In the end turned out to be the lodash method used in the update function.
angular-fullstack used _.merge whilst mean.io used _.extend after changing the angular-fullstack code to use _.extend the sub-arrays were correctly stored into MongoDB
For anyone else using angular-fullstack. Edit your server endpoint controller e.g. server/api/products/product.controller.js
Then find the function for update and replace the _.merge with _.extend e.g.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Product.findById(req.params.id, function (err, product) {
if (err) { return handleError(res, err); }
if(!product) { return res.send(404); }
var updated = _.extend(product, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, product);
});
});
};

Resources