Aggregate function duplicates items in ng-repeat on page refresh. Need to figure out how to stop the duplication. Angularjs Mongodb mongoose

All we need is an easy explanation of the problem, so here it is.

I have items in a collection that run through this aggregate to find them with an average rating and to find them when they have not yet been commented on / rated yet.

It works, but everytime I refresh the page now, it makes a duplicate on display within the ng-repeat of each item in the bourbon collection. The actual amount of items in the bourbon collection is not changing. How do I stop this?
SERVER SIDE:
bourbon schema:

'use strict';

var mongoose = require('mongoose'),
    BourbonSchema = null;

module.exports = mongoose.model('Bourbon', {
    name:  {type: String, required: true},
    blog:  {type: String, required: true},
    photo: {type: String, required: true, default:'http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg'},
    location: {type: String, required: true},
    distillery: {type: String, required: true},
    comments: [{type: mongoose.Schema.ObjectId, ref: 'Comments'}],
    rating : {type: Number}
});

var Bourbon = mongoose.model('Bourbon', BourbonSchema);
module.exports = Bourbon;

comment/rating schema:

'use strict';

var mongoose = require('mongoose');

module.exports = mongoose.model('Comment', {
    bourbonId:   {type: mongoose.Schema.ObjectId, ref: 'Bourbon'},
    userId:   {type: mongoose.Schema.ObjectId, ref: 'User'},
    text:     {type: String, required: true},
    createdAt: {type: Date,  required: true, default: Date.now},
    rating  : {type: Number, required: true},
    votes:     {type: Number, default: 0}
});

aggregate function:

'use strict';

var Bourbon = require('../../../models/bourbon'),
    Comment = require('../../../models/comment'),
    DataStore = require('nedb'),
    db = new DataStore(),
    async = require('async');

module.exports = {
    description: 'Get Bourbons',
    notes: 'Get Bourbons',
    tags: ['bourbons'],
    handler: function(request, reply){

        async.series(
            [
                function(callback){
                    Bourbon.find({},function(err,results){
                        //if (err) callback(err);
                        async.eachLimit(results,10,function(result,callback){
                            var plain = result.toObject();
                            plain.bourbonId = plain._id.toString();
                            plain.avgRating = 0;
                            delete plain._id;

                            db.insert(plain,callback);
                        },callback);
                    });
                },

                function(callback){
                    Comment.aggregate(
                        [
                            {$group:{
                                _id: '$bourbonId',
                                avgRating:{$avg:'$rating'}
                            }}
                        ],
                        function(err,results){
                            async.eachLimit(results,10,function(result,callback){
                                db.update(
                                    {bourbonId: result._id.toString()},
                                    {$set: {avgRating: result.avgRating}},
                                    callback                   
                                );
                            },callback);                 
                        }
                    );
                }
            ],

            function(err){
                //if (err) callback(err);
                db.find({}, {_id: 0}, function(err, bourbons){
                    console.log('DOOOOCS', bourbons);
                    reply ({bourbons:bourbons});
                });
            });
    }
};

CLIENT side

factory:

(function(){
    'use strict';

    angular.module('hapi-auth')
        .factory('Bourbon', ['$http', function($http){

            function create(bourbon){
                return $http.post('/admin', bourbon);
            }

            function all(){
                return $http.get('/admin');
            }

            return {create:create, all:all};
        }]);
})();

controller:

(function(){
    'use strict';

    angular.module('hapi-auth')
        .controller('AdminCtrl', ['$scope', 'Bourbon', function($scope, Bourbon){
            //$scope.blog.photo = [];
            $scope.bourbon = {};
            $scope.bourbons = [];

            Bourbon.all().then(function(res){
                $scope.bourbons = res.data.bourbons;
                console.log(res.data.bourbons);
            });

            $scope.createBourbon = function(bourbon){
                console.log('BOURBB', bourbon);
                Bourbon.create(bourbon).then(function(res){
                    console.log('bourboonnnn', res.data);
                    $scope.bourbons.push(res.data);

                });
            };


        }]);
})();

jade:

.row
   .small-4.columns
   .small-4.columns
      .review
         .insertContainer(ng-repeat='bourbon in bourbons')
            .adminName Name: {{bourbon.name}}
            img.bourbonImg(src='{{bourbon.photo}}')
            .adminBlog {{bourbon.blog.slice(0,200)}} ...
   .small-4.columns

trying to stop duplication of items in bourbon collection on page refresh….
Not sure if I need to implement some sort of replace method here.. a bit stuck now..

This is what the console log in the controller / aggregate function returns:

DOOOOCS [ { name: 'woodford reserve',
    location: 'kentucky',
    distillery: 'woodford',
    blog: 'fjkd;asdljfkld;ksdfj',
    __v: 0,
    comments: [],
    photo: 'http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg',
    bourbonId: '54c2aa5556267e0000b5618c',
    avgRating: 0 },
  { name: 'woodford reserve',
    location: 'kentucky',
    distillery: 'woodford',
    blog: 'fjkd;asdljfkld;ksdfj',
    __v: 0,
    comments: [],
    photo: 'http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg',
    bourbonId: '54c2aa5556267e0000b5618c',
    avgRating: 0 } ]

it seems to duplicate each item in the Bourbons collection whenever this function is run… , which it runs on page refresh as the Bourbon.all within the angular controller.

this is the bourbon in the collection, before it runs through the aggregate:

{
    "_id" : ObjectId("54c2aa5556267e0000b5618c"),
    "name" : "woodford reserve",
    "location" : "kentucky",
    "distillery" : "woodford",
    "blog" : "fjkd;asdljfkld;ksdfj",
    "comments" : [],
    "photo" : "http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg",
    "__v" : 0
}

updated jade w/ track by:

.row
   .small-4.columns
   .small-4.columns
      .review
      .insertContainer(ng-repeat='bourbon in bourbons | unique: bourbon.bourbonId | filter: query')

            .adminName Name: {{bourbon.name}}
            img.bourbonImg(src='{{bourbon.photo}}')
            .adminBlog {{bourbon.blog.slice(0,200)}} ...
   .small-4.columns

this allows the items to show on the first load, but on any refresh / reload, I now get this error:

Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: bourbon in bourbons track by bourbon.bourbonId | filter:query, Duplicate key: 54c2aa5556267e0000b5618c, Duplicate value: {"name":"woodford reserve","location":"kentucky","distillery":"woodford","blog":"fjkd;asdljfkld;ksdfj","__v":0,"comments":[],"photo":"http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg","bourbonId":"54c2aa5556267e0000b5618c","avgRating":0}
http://errors.angularjs.org/1.3.8/ngRepeat/dupes?p0=bourbon%20in%20bourbons…C%22bourbonId%22%3A%2254c2aa5556267e0000b5618c%22%2C%22avgRating%22%3A0%7D
    at http://localhost:7070/vendor/angular/angular.js:63:12
    at ngRepeatAction (http://localhost:7070/vendor/angular/angular.js:24483:21)
    at Object.$watchCollectionAction [as fn] (http://localhost:7070/vendor/angular/angular.js:14092:13)
    at Scope.$digest (http://localhost:7070/vendor/angular/angular.js:14225:29)
    at Scope.$apply (http://localhost:7070/vendor/angular/angular.js:14488:24)
    at done (http://localhost:7070/vendor/angular/angular.js:9646:47)
    at completeRequest (http://localhost:7070/vendor/angular/angular.js:9836:7)
    at XMLHttpRequest.requestLoaded (http://localhost:7070/vendor/angular/angular.js:9777:9)

these are the bourbon items being returned in the browser console. all of them have the same bourbonId, starting with index 0. Unique filter is returning 2 bourbon objects now, (the original and one of the duplicates?) instead of all of them… :

[Object, Object, Object, Object, Object]0: Object$$hashKey: “object:6”v: 0avgRating: 4blog: “fjkd;asdljfkld;ksdfj”bourbonId: “54c2aa5556267e0000b5618c”comments: Array[0]distillery: “woodford”location: “kentucky”name: “woodford reserve”photo: “http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpg“__proto: Object1: Object__v: 0avgRating: 4blog: “fjkd;asdljfkld;ksdfj”bourbonId: “54c2aa5556267e0000b5618c”comments: Array[0]distillery: “woodford”location: “kentucky”name: “woodford reserve”photo: “http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpgproto: Object2: Object$$hashKey: “object:7”v: 0avgRating: 0blog: “fjkd;asdljfkld;ksdfj”bourbonId: “54c2aa5556267e0000b5618c”comments: Array[0]length: 0__proto: Array[0]distillery: “woodford”location: “kentucky”name: “woodford reserve”photo: “http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpgproto: Object3: Object__v: 0avgRating: 0blog: “fjkd;asdljfkld;ksdfj”bourbonId: “54c2aa5556267e0000b5618c”comments: Array[0]length: 0__proto__: Array[0]distillery: “woodford”location: “kentucky”name: “woodford reserve”photo: “http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpgproto: Object4: Object__v: 0avgRating: 0blog: “fjkd;asdljfkld;ksdfj”bourbonId: “54c2aa5556267e0000b5618c”comments: Array[0]length: 0__proto__: Array[0]distillery: “woodford”location: “kentucky”name: “woodford reserve”photo: “http://aries-wineny.com/wp-content/uploads/2014/09/woodford-reserve.jpgproto: Objectlength: 5__proto__: Array[0]

I think I’ve narrowed this to being an issue on the server side, within the controller / aggregate listed above. I need to find a way to replace the updated documents, instead of creating a new one each time the Bourbon.all is called / run on the client side / browser … trying to tackle it now. Any tips, much appreciated.

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

That seems to be issue from server side add track by $index will fix the issue.

ng-repeat='bourbon in bourbons track by $index

But the thing is, it will show the duplicate records.

Better you use AngularUI angular.unique filter

First add the JS from link & then add ‘ui.filters’ module inside app dependancy.

ng-repeat='bourbon in bourbons | unique:'bourbonId'

Hope this will solve your problem.

Thanks.

Method 2

This seems to be just a case of taking an example too literally. You’re ending up with duplicated output on each request because that is how the store where the results are coming from is scoped.

So instead of this:

'use strict';

var Bourbon = require('../../../models/bourbon'),
    Comment = require('../../../models/comment'),
    DataStore = require('nedb'),
    db = new DataStore(),          // <-- globally scoped in the module :(
    async = require('async');

module.exports = {
    description: 'Get Bourbons',
    notes: 'Get Bourbons',
    tags: ['bourbons'],
    handler: function(request, reply){

        async.series(
            [
                function(callback){

Rather do this:

'use strict';

var Bourbon = require('../../../models/bourbon'),
    Comment = require('../../../models/comment'),
    DataStore = require('nedb'),
    async = require('async');

module.exports = {
    description: 'Get Bourbons',
    notes: 'Get Bourbons',
    tags: ['bourbons'],
    handler: function(request, reply){

        db = new DataStore();   // <--- Init store within request scope :)

        async.series(
            [
                function(callback){

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply