javascriptbackbone.jsbackbone.js-collectionsbackbone-model

Update a model but persist the whole Backbone collection


My API response:

{
  firstName: '',
  lastName: '',
  notifications: [
    {
      category: '1',
      subcategory: '',
      source: '',
      optInDate: '',
      optOutDate: '',
      active: ''
    },
    {
      category: '2',
      subcategory: '',
      source: '',
      optInDate: '',
      optOutDate: '',
      active: ''
    }
  ]
}

My corresponding Backbone implementation for the response is:

var Notification = Backbone.Model.extend({
  initialize: function (args) {  },
});

var Notifications = Backbone.Collection.extend({
  model: Notification,
  url: '/xyz/abc',

  parse: function (data) {
    return data.notifications;
  },
});

I want to only update the "category 1" model (this is always true) with say active: true but persist the whole collection in my request payload and not just the changed model. How do we accomplish this with backbone?

I have tried getting that particular model and doing a model.set({active: true}) and calling model.save() but that sends only that model to the server.

I need the whole response to be persisted back to the server along with the updated model.

EDIT:

I was able to achieve this with @Khang's help in his answer below. However I forgot to mention that I need to send other attributes too along with the collection i.e, firstName, lastName from the above response.

I'm overriding the parse method to include them as below:

parse: function(data) { 
  this.firstName = data.firstName; 
  this.lastName = data.lastName; 
  return data.notifications; 
} 

When I call Backbone.sync, it still sends just the collection in the payload. Is that because I am returning just the notifications from data object in the parse method?


Solution

  • I'd say don't use sync like Khang suggests and overriding the Backbone.sync function should be done with extreme caution since it is shared by every models and collections.

    While it works for your use-case right now, it's more of a patch in the long run.


    the API expects the whole DTO to be sent back in the payload to maintain RESTFul

    If the API was truly RESTful, it would provide an endpoint to manage the notifications individually.

    As a reference to future readers, make an endpoint to manage models individually. This is the way to go when creating a REST API. I go over the pros and cons of this technique with Backbone in mind in another answer.

    API is not open to changing their implementation

    Since you can't, that means the API response is representing a model, not a collection. Backbone offers predefined behaviours based on a RESTful API where an object should be a model, and an array of objects should be a collection (exception being when receiving metadata with a collection like page count, etc.) A Backbone Collection doesn't have a save function for this reason, it shouldn't be saved all at once.

    In your case, it looks like a user model would probably do the job.

    Note that there's a lot of alternatives to implement a collection within a model. Here's one quick example.

    var UserModel = Backbone.Model.extend({
        urlRoot: '/xyz/abc',
        initialize: function(attrs, options) {
            // init a collection within the model
            this.notifications = new Notifications((attrs || {}).notifications);
            // update the collection whenever the model is synced.
            this.listenTo(this, 'sync', this.onSync());
        },
    
        // Useful abstraction that doesn't interfere with the normal use-case
        saveNotifications: function() {
            return this.save({
                notifications: this.notifications.toJSON()
            }, { patch: true });
        },
    
        onSync: function() {
            this.notifications.reset(this.get('notifications'));
        }
    });
    

    You can use the model like any other

    var user = new UserModel({ id: 'user-id-1' });
    user.fetch();
    

    And when you need the notifications, they're already available within a handy collection.

    var notif = user.notifications.findWhere({ category: '1' });
    if (notif) {
        notif.set({ active: true });
        user.saveNotifications();
    }
    

    Backbone events like sync on the user model and reset on the notifications collection will trigger correctly.

    When dealing with lots of data, there are downsides to this technique, which saving the collection all at once should bring to the light anyway. Lazy loading and paginating are ways to improve performance in this situation, and obviously, following REST best practices will help a lot.

    At our company, our APIs were heavily inspired by "Build APIs you won't hate" and I'm glad that we're using this book.