简体   繁体   中英

Nested fetching in Backbone.js

I have two API endpoints:

/products (with Product model associated with it), and

/stockitems (which corresponds to StockItems collection in my Backbone.js app).

To render StockItems collection view I first fetch the collection and then for every model in it fetch a Product (eg /products/1499 ) to get some additional information.

My first attempt was as follows.

var stock = new App.Collections.StockItems();
stock.fetch().success(function(){
  for (var i in stock.models) {
    var product = new App.Models.Product({id: stock.models[i].get('product_id')});
    product.fetch().success(function(){
      stock.models[i].set('img', product.get('image_url_1'));
    });
  }
});

However, this way only the last of stock.model 's gets updated with additional attribute. I had a hunch if I waited for all of them to fetch and then updated, it would work. This was my second attempt.

var stock = new App.Collections.StockItems(), 
    product = [],
    count;
stock.fetch().success(function(){
  count = stock.models.length;
  for (var i in stock.models) {
    product[i] = new App.Models.Product({id: stock.models[i].get('product_id')}); 
    product[i].fetch().success(function(){
      count--;
      if (count === 0) {
        for (i in stock.models) {
          stock.models[i].set('img', product[i].get('image_url_1'));
        }
      } 
    });
  }
});

It works now, however I'm not sure I understand why I need that counter and additional loop. I also feel like there should be a better way to do it. Maybe including fetch instructions in Backbone views?

Below are my models/collections/views definitions.

window.App = {
  Models: {},
  Views: {},
  Collections: {}
};

App.Models.Product = Backbone.Model.extend({
    urlRoot: 'http://mysite/api/products'
});

App.Models.StockItem = Backbone.Model.extend({});

App.Collections.StockItems = Backbone.Collection.extend({
    model: App.Models.StockItem,
    url: 'http://mysite/api/stock'
});

App.Views.StockItem = Backbone.View.extend({
    tagName: 'li',
    template: _.template( $('#stockItemTemplate').html() ),
    render: function() {
        this.$el.html( this.template(this.model.toJSON()) );
        return this;
    }
});

App.Views.StockItems = Backbone.View.extend({
    tagName: 'ul',
    initialize: function() {
        this.$el.empty();
    },
    render: function() {
        this.$el.empty();
        this.collection.each(this.addOne, this);
        return this;
    },
    addOne: function(stock_item) {
      var stock_item = new App.Views.StockItem({ model: stock_item });
      this.$el.append( stock_item.render().el );
    }
});

Now I understand that you want to show product image when displaying stock item, so you ask each model in stock items to fetch the image in a loop.

This is not efficient, man. Imagine how many requests you'll send to server!

Don't fetch multiple times. Do it in once. The nested fetches are really unnecessary.

The Simple Solution

It looks you don't need product view in the same time. So the simplest solution would just deliver the JSON collection from server, with product image url in each model. Done.

The Advanced Solution

I think the simple solution is good enough. Adding an extra field of image url in model from server takes little resource. So, even if you have time that no image needed, you can just hold the image data there with no harm.

However, if you still want more control, you can set a param to ask server to add image or not

// StockItemsCollection
url: function(){
  return 'http://example.com/stock' + this.params;
};

params: ''

When you want the image, you can add &with_image=true to param before fetching, and the server could respond with this param to deliver image or not. Quite flexible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM