Search code examples
angularjsnode.jsmongodbangular-resource

Angular Resource update method with an array as a parameter


I have been googleing this for a few weeks with no real resolution.

I am sure someone will mark this a duplicate, but I am not sure it really is, maybe I am just being too specific, anyway here goes.

I am using angular in a node-webkit app that I am building. I have an api built in express and I am using MongoDB (@mongolab) with Mongoose for the DB.

I had this working fine as long as all of the data types were simple strings and numbers. but I had to restructure the data to use arrays and complex objects. After restructuring the data I was able to get post API calls to work fine, but I cannot get my PUT calls to work at all.

The data looks like this:

itemRoles was an array, but I thought it was throwing the error I am getting now, so I converted it back to a string.

itemStats is causing the problem. Angular is looking for an object, but itemStats is an array (I think anyway). itemStats used to be a string as well, but its easier to work with in my view if it is an array of objects with key:value pairs, which is why I altered it.

I should note I am new to MongoDB as well, first time using it.

{
  "_id": {
    "$oid": "55a10b9c7bb9ac5832d88bd8"
  },
  "itemRoles": "healer,dps",
  "itemRating": 192,
  "itemName": "Advanced Resolve Armoring 37",
  "itemClass": "consular",
  "itemLevel": 69,
  "itemStats": [
    {
        "name": "Endurance",
        "value": 104,
        "_id": {
            "$oid": "55a10b9c7bb9ac5832d88bda"
        }
    },
    {
        "name": "Willpower",
        "value": 124,
        "_id": {
            "$oid": "55a10b9c7bb9ac5832d88bd9"
        }
    }
  ],
  "__v": 0
}

The Mongoose Schema looks like this:

var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

//var stats = new Schema({
    //name: String,
    //value: Number
//});

var armoringSchema   = new Schema({
    itemType: String,
    itemClass: String,
    itemRoles: String,
    itemLevel: Number,
    itemName: String,
    itemRating: Number,
    itemStats: [{ name:String, value:Number}]
});

module.exports = mongoose.model('Armor', armoringSchema);

Express API Route:

/ on routes that end in /armors/:id
// ----------------------------------------------------
router.route('/armors/:id')
// get method omitted
// update the armoring with specified id (accessed at PUT http://localhost:8080/api/armors/:id)
.put(function(req, res) {

    // use our armor model to find the armor we want
    Armoring.findById({_id: req.params.id}, function(err, armor) {

        if (err) {
            return res.send(err);
        }

        for(prop in req.body) {
            armor[prop] = req.body[prop];
        }


        // save the armor
        armor.save(function(err) {
            if (err) {
                return res.send(err);
            }

            res.json({success:true, message: 'Armor updated!' });
        });

    });
})

Resource Factory:

swtorGear.factory('armoringFactory', ['$resource', function ($resource) {
    return $resource('http://localhost:8080/api/armors/:id', {}, {
        update: { method: 'PUT', params: {id: '@_id'}},
        delete: { method: 'DELETE', headers: {'Content-type': 'application/json'}, params: {id: '@_id'}}
    });
}]);

Route for editing:

.when('/edit/armor/id/:id', {
        templateUrl: 'views/modelViews/newArmor.html',
        controller: 'editArmorCtrl',
        resolve: {
            armoring: ['$route', 'armoringFactory', function($route, armoringFactory){
                return armoringFactory.get({ id: $route.current.params.id}).$promise;
            }]
        }
    })

Contoller (just the save method, the first part of the controller populates the form with existing data):

$scope.save = function(id) {
    $scope.armor.itemStats = [
        $scope.armor.stats1,
        $scope.armor.stats2
    ];

    $scope.armor.itemRoles = '';
    if($scope.armor.role.tank) {
        $scope.armor.itemRoles += 'tank';
    }

    if($scope.armor.role.healer) {
        if($scope.armor.itemRoles != '') {
            $scope.armor.itemRoles += ',healer';
        } else {
            $scope.armor.itemRoles += 'healer';
        }
    }

    if($scope.armor.role.dps) {
        if($scope.armor.itemRoles != '') {
            $scope.armor.itemRoles += ',dps';
        } else {
            $scope.armor.itemRoles += 'dps';
        }
    }

    console.log($scope.armor);

    $scope.armor.$update(id)
        .then(function(resp) {
            if(resp.success) {
                var message = resp.message;
                Flash.create('success', message, 'item-success');
                $scope.armors = armoringFactory.query();
            } else {
                var message = resp.message;
                Flash.create('success', message, 'item-success');
            }
        });
}

Formatted data being sent via PUT method (from console.log($scope.armor) ):

enter image description here

Error on save:

enter image description here


Solution

  • I haven't seen nesting schemas in the way that you're doing it. Here's something to try (hard to say if this is it for sure, there's a lot going on):

    var armoringSchema   = new Schema({
        itemType: String,
        itemClass: String,
        itemRoles: String,
        itemLevel: Number,
        itemName: String,
        itemRating: Number,
        itemStats: [{
          name: String,
          value: Number
        }]
    });
    

    Also we need to pass in an object to $update instead of just a number. Change $scope.armor.$update(id) to $scope.armor.$update({id: id}).