Search code examples
jsonbackbone.jsfetch

Backbone: fetching from URL in router gets undefined, but it works when collection gets JSON from a variable


From a JSON stored in a variable I can get the name of the current id from a router function called show: function(id). However, when I fetch collection from an URL instead of using a JSON variable I get an undefined TypeError.

console.log(this.collection.get(id).get('name'));

What I have seen is that when I use a JSON variable the show function works fine, but when I fetch from URL, show function executes after fetch succeed.

What I am doing wrong? Why fetching from URL gets undefined? How can I make it work?

The following code is fictional, it only shows the relevant part of my code. See the two cases at the end of the code block.
jsFiddle here

// Data 1 with variable
var heroes = [
  {"id": "1", "name": "Batman"},
  {"id": "2", "name": "Superman"},
];

// Data 2 from url: http://example.com/heroes.json
[
  {"id": "1", "name": "Batman"},
  {"id": "2", "name": "Superman"},
];

HeroesCollection = Backbone.Collection.extend({
  model: HeroesModel,
  url: 'http://example.com/heroes.json'
});

HeroesRouter = Backbone.Router.extend({
  // I use two shows to graphic this example
  routes: {
    '': 'index',
    ':id': 'show'
  },

  initialize: function(options) {
    this.collection = options.collection;
    this.collection.fetch();
    // this.collection.fetch({async:false}); this fixes my problem, but I heard it is a bad practice
  },

  index: function() {

  },

  show: function(id) {
    console.log(this.collection.get(id).get('name'));
        // Case #1: When Collection loads from a Variable 
        // id 1 returns: 'Batman'

        // Case #2: When Collection fetchs from URL, id 1 returns:
        // TypeError: this.collection.get(...) is undefined 
  }

});

// Case #1: collection loads JSON from a variable
var heroesCollection = new HeroesCollection(heroes);
// Case #2: collection loads JSON with fetch in router's initialize
// var heroesCollection = new HeroesCollection();
var heroesRouter = new HeroesRouter({collection: heroesCollection});

Solution

  • How about this? It's been awhile, but this seems like a better approach to what you are trying to achieve. The basic concept is that once you navigate to your show route, it will execute show. This method will create a new, empty collection, and then fetch the data for it. Along with that, we pass in a success method (as François illustrated) which will execute when the request is finished with the JSON (which creates a collection of Heros).

    I believe the reason you were running into the issue with the remote data is that you were trying to access this.collection before it was populated with data from the request.

    You have to remember the request is asynchronous, which mean code execution continues while the request is processing.

    HeroesCollection = Backbone.Collection.extend({
      model: HeroesModel,
      url: 'http://example.com/heroes.json'
    });
    
    HeroesRouter = Backbone.Router.extend({
      routes: {
        '': 'index',
        ':id': 'show'
      },
    
      index: function() {
    
      },
    
      show: function(id) {
        this.herosCollection = new HerosCollection();
        this.herosCollection.fetch({
          success: function(collection, response, options) {
            console.log(this.get(id).get('name'));
          }
        });
      }
    
    });