Backbone: How to update a collection view when collection changes?

21,616

Bulk replacements are easier and more efficient using reset:

reset collection.reset([models], [options])

Adding and removing models one at a time is all well and good, but sometimes you have so many models to change that you'd rather just update the collection in bulk. Use reset to replace a collection with a new list of models (or attribute hashes), triggering a single "reset" event at the end. Returns the newly-set models. For convenience, within a "reset" event, the list of any previous models is available as options.previousModels.

So instead of using set to merge the changes and generate a bunch of 'add' and 'remove' events, using reset and listen for 'reset' event:

// In the view's `initialize`...
this.listenTo(this.collection, 'reset', this.render);

and then render can redraw the whole thing and you'd say:

suggestionsView.collection.reset(this.getSuggestions(query))
// ------------------------^^

to refresh things.


Some clarification from the comments: Models don't generate 'add' events, only collections trigger those. Models trigger 'change' events when their properties change, collections trigger 'add' and 'remove' events when models are added and removed (respectively) from them; collections can also trigger 'change' events because they forward all the events from their enclosed models:

Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience.

So if you want to use Collection#set then you'd want three handlers in your view:

  1. this.listenTo(this.collection, 'add', ...): A new model has been added to the collection so render it.
  2. this.listenTo(this.collection, 'remove', ...): A model has been removed from the collection so remove its part of the view.
  3. this.listenTo(this.collection, 'change', ...): A model has changed so update its part of the view.

If you're only working with small collections then reset might be less work. If your collections are larger or the view changes are more expensive then dealing with the three events separately might be best.

In any case, if you're using sub-views, you'll want to maintain a list of them somewhere in the parent view so that you can call remove on them to make sure things are properly cleaned up. If you're destroying the models when removing them from the collection, you could have the sub-views bind to their model's 'destroy' events and remove themselves as needed.

The Catalog of Events might be worth a review to see what events are triggered at which times.

Share:
21,616
Alex B
Author by

Alex B

Front End Developer and Language Enthusiast from London, UK. Figuring out how nomadic I want to be. Based in Montpellier, France. Enjoying all things language: spoken and coded ;).

Updated on December 11, 2020

Comments

  • Alex B
    Alex B over 3 years

    I'm relatively new to Backbone.js. I'm initializing a collection view and passing in a collection at creation time.

    suggestionsView = new TreeCategoriesAutoSuggest.Views.Suggestions({
        collection: new App.Collections.Suggestions(this.getSuggestions(query))
    });
    

    I then render the collection view. Each time a user enters a query into a text box the collection is regenerated and assigned to the collection view using:

    suggestionsView.collection.set(this.getSuggestions(query));
    

    This takes care of the adding/removing of models in the collection but how do I manage the adding/removing of views for the added/removed models?

    I should mention that I have used the this.collection.on("add") listener in the collection view. But this gets triggered for every model that is added. I also tried this.model.on("change") from within the individual view but this is not fired when models are added/removed from collections.

    Any help/guidance appreciated!

    Update

    I am now using:

    suggestionsView.collection.reset(this.getSuggestions(query));
    

    And when the reset event is fired I'm removing the suggestion sub views, re-initializing them for the new collection and re-rendering the collection view.

    handleReset: function(){
        console.log("reset");
        this.cleanupOldViews();
        this.initViews();
    },
    
    initViews: function(){
        this.collection.each(function(suggestion){
            this.suggestionViews.push(new TreeCategoriesAutoSuggest.Views.Suggestion({
                model: suggestion
            }));
        },this);        
    },
    
    cleanupOldViews: function(){
        _.each(this.suggestionViews,function(suggestionView){
            suggestionView.remove()
        },this);
    
        this.suggestionViews = [];
    }
    

    So you think I don't need to worry about destroying the models?

  • Alex B
    Alex B over 10 years
    But isn't it a bad idea to render the whole list again? In my case I don't have that many models in the collection. Will reset remove the corresponding views from memory?
  • mu is too short
    mu is too short over 10 years
    Depends on the views. Keeping track of the sub-views and calling remove on them is usually a good idea. I thought you didn't want to deal with a bunch of individual 'add' and 'remove' events, did I misunderstand?
  • Alex B
    Alex B over 10 years
    No you didn't misunderstand. Perhaps I wasn't clear. When I listened for the "add" event in the collection the callback gets invoked for each model. However, when listening on the model for the "add" event nothing happens. In Backbone collection.set has: (model = toAdd[i]).trigger('add', model, this, options); which made me think it would trigger on the model and therefore that's where I should listen.
  • mu is too short
    mu is too short over 10 years
    Models don't trigger 'add' events since nothing is added (in the Backbone sense) to models, collections trigger 'add' events. I've added some clarifications above.
  • Alex B
    Alex B over 10 years
    Thank you! My brain is fried, of course that makes sense! I'll keep track of the subViews and remove them when needed. Reset seems better however I can't see a destroy event triggered on the individual models so am I right to assume they don't get removed? The models array simply gets reset to [].
  • mu is too short
    mu is too short over 10 years
    You usually have to destroy models manually because that usually means "I want this model dead and gone from everywhere including the server". A simple for(var i = 0; i < this.subs.length; ++i) this.subs[i].remove() in your render combined with keeping the sub-views in this.subs should be about right for your case. I could whip up a quick'n'dirty demo if that would clear things up.