1

I have thig angularJS frontend and I use express, node and mongo on the backend.

My situation looks like:

//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]

$scope.update = function() {
    $scope.things.forEach(function(t) {
        Thing.create({
            title: t.title,
            //other values here
        }, function() {
            console.log('Thing added');
        })
    })
};

//where Thing.create its just an $http.post factory

The HTML part looks like:

//html part
<button ng-click="update()">Update Thing</button>

Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.

Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.

Or maybe its other way to do this?

Adam Zerner
  • 17,797
  • 15
  • 90
  • 156
Hiero
  • 2,182
  • 7
  • 28
  • 47
  • Just asking for clarification , when you clicked second time, do you want to update just an array item or whole array? Now you are posting whole array item by item. You can make a item counter and store in a variable in first click. Then you can check if item has already posted you will be able to update it. – İlker Korkut Aug 28 '15 at 08:00
  • I want to update the things that was edited and post the things that where added (the user has ability to push new things) – Hiero Aug 28 '15 at 08:01
  • When I post the questions I add a new key posted: true, and I check if thing has this key and update else I'm posting and adding this key, its that right? – Hiero Aug 28 '15 at 08:09
  • yes, if it's suitable for you you can add posted:true property on your objects. – İlker Korkut Aug 28 '15 at 08:17
  • yep, when i push I use lodash omit so I omit pushing the posted: true – Hiero Aug 28 '15 at 08:25
  • 3
    I think the posted property might not be needed. If an item is already saved then it will have an ID. So you just need to check the ID to decide whether to post or put. – David Jones Aug 28 '15 at 10:17
  • @DavidJones it is possible, but he need to store posted ids and check again for extra. So in this situation there are several ways to do it. He may choose most effective and suitable way. – İlker Korkut Aug 28 '15 at 11:03
  • @İlkerKorkut in a sense yes he would, but once a 'thing' is saved then the object in the '$scope.things' array would be updated with the saved instance. This would then contain the ID and maybe some other information like a create date etc. So when the foreach loop runs again it will be easy to check the existence of an ID. At least thats how I would do it! – David Jones Aug 28 '15 at 13:01
  • How is the user changing the things? Wouldn't be simpler to call update with the changed thing in parameter (easily done with ng-change) instead of making many useless requests. For the create versus update you can always update and use mongoDb upsert feature, so that if it does exist it creates it (internally it uses the unique objectId to do so). Also, can you provide your html, Thing service code as well as the backend code that is called from the service? – Anthony Garant Aug 30 '15 at 19:35
  • @AnthonyGarant user its changing some inputs, adding some values by pressing a button. Do you think its better to update when the user change or when all the 'Things' are changed? – Hiero Aug 31 '15 at 08:35

2 Answers2

2

I see a few decisions to be made:

1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?

2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.

3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?


1

To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.

The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.

Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.


2

The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).

The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.

  • When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
  • When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
  • When a user changes an existing Thing, the dirty property would be set to true.

Then, using the dirty property you could only make requests for the Thing's that are dirty:

$scope.things.forEach(function(thing) {
  if (thing.dirty) {
    // make request
  }
});

The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.


3

If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).

With this, you can conditionally call create or update like so:

$scope.things.forEach(function(thing) {
  if (thing._id) {
    Thing.update(thing._id, thing);
  }
  else {
    Thing.create(thing);
  }
});

Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.

In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:

Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
  ...
});

See this SO post.

Community
  • 1
  • 1
Adam Zerner
  • 17,797
  • 15
  • 90
  • 156
1

@Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.

Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.

  1. To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
  2. You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
  3. Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
  4. To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.

Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.

$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
    var $scope.saving = true; // To display the saving... message
    Thing.update({ // This service call your method that update with upsert
        title: changedThing.title,
        //other values here
    }).then( // If you made an http request, I suppose it returns a promise.
           function success() {
               $scope.saving = false;
               console.log('Thing added');
           },
           function error() {
               //handle errors               
           })
};
Anthony Garant
  • 614
  • 7
  • 13
  • Fair points. There's a fantastic article by the Neilson Norman Group on the topic [here](http://www.nngroup.com/articles/efficiency-vs-expectations/) that I defer to and completely agree with. My end-opinion is that if it's important to prevent the user from accidentally losing progress, use the autosave _as well as_ the button. Otherwise, just the button. Alternatively, if there's enough user feedback associated with the autosave and you think it's appropriate, I think that could be a sensible approach too. – Adam Zerner Sep 01 '15 at 05:02