29

I just started learning Ember.js (bought the PeepCode screencast), and am learning quite smoothly from it, but ran into a problem when trying to write my first Ember app.

Here's the (nested) route mapping:

App.Router.map(function () {
    this.resource('bases', { path: '/' }, function () {
        this.resource('base', { path: ':base_id' }, function () {
            this.resource('places', function () {
                this.resource('place', { path: ':place_id' });
            });
        });
    });
});

This allows urls like this: domain.com/#/yokota-ab-japan/places/4c806eabd92ea093ea2e3872

yokota-ab-japan is the id of a base (an Air Force base in Japan) 4c806eabd92ea093ea2e3872 is the id of a venue on Foursquare

When the places route is hit, I setup the data with a call to the foursquare api, iterate over the JSON to create an array of App.Place objects, and return that array.

App.PlacesRoute = Ember.Route.extend({
    model: function () {
        var placesData = Ember.A();
        $.getJSON('https://api.foursquare.com/v2/venues/search?ll=35.744771,139.349456&query=ramen&client_id=nnn&client_secret=nnn&v=20120101',
            function (data) {
                $.each(data.response.venues, function (i, venues) {
                    placesData.addObject(App.Place.create({ id: venues.id, name: venues.name, lat: venues.location.lat, lng: venues.location.lng }));
                });
            });

        return placesData;
    }
});

That seems to work well. I display the placesData array using this template:

<script type="text/x-handlebars" data-template-name="places">
    <div>
        {{#each place in controller}}
            {{#linkTo 'place' place}}
                {{place.name}}
            {{/linkTo}}
        {{/each}}
    </div>

    {{outlet}}
</script>

The linkTo links to the individual place, where I want to show more details about a place. Here's the route I setup to pull data about the single place, populate a single App.Place object, and return it:

App.PlaceRoute = Ember.Route.extend({
    model: function (params) {
        console.log('place route called');

        var place;
        $.getJSON('https://api.foursquare.com/v2/venues/' + params.place_id + '?client_id=nnn&client_secret=nnn',
            function (data) {
                var v = data.response.venue;
                place = App.Place.create({ id: v.id, name: v.name, lat: v.location.lat, lng: v.location.lng });
            });

        return place;
    }
});

My problem is, PlaceRoute doesn't get called when the user clicks a link to it, but it does get called when the page is refreshed on this route.

According to the PeepCode Ember.js screencast (at ~12:25 into the video) it states that "controllers rarely ever make calls to data, route objects handle that", so I think I'm correct in putting my ajax calls in the routes (I've seen so many differing tutorials online, but since Peepcode consulted with the Ember.js creators, I went with it as my primary learning source).

If this isn't correct, or could be done better, please let me know.

Patsy Issa
  • 11,113
  • 4
  • 55
  • 74
Chaddeus
  • 13,134
  • 29
  • 104
  • 162

3 Answers3

49

Ember Documentation on the model hook:

"A hook you can implement to convert the URL into the model for this route."

This explains why the model hook is just called on page refresh. But this confuses a lot of people :-). So, this is not the right hook in this case. I think you should use the setupController hook for this case. Try this:

App.PlaceRoute = Ember.Route.extend({
    setupController: function (controller, model) {
        console.log('place route called');

        var place;
        $.getJSON('https://api.foursquare.com/v2/venues/' + model.get('place_id') + '?client_id=nnn&client_secret=nnn',
            function (data) {
                var v = data.response.venue;
                place = App.Place.create({ id: v.id, name: v.name, lat: v.location.lat, lng: v.location.lng });
            });

        controller.set("model", place);
    }
});

Additional 2 cents from myself: Why is the model hook just executed, when the app is entered via URL/direct browser navigation?

Ember has the assumption, that you do not necessarily make a call to the model hook, if you use {{linkTo}}. In your places template you use {{#linkTo 'place' place}} {{place.name}} {{/linkTo}}. You are passing a place object to your route. Ember assumes that this is the same object, that you would get when executing your model hook. So from Embers View there is no need to call the model hook. So if you want to perform retrieval logic even when you use linkTo, use the setupController hook.

mavilein
  • 11,648
  • 4
  • 43
  • 48
  • Thanks for the explanation, helps understand how things are working. Makes sense that since I'm passing it a `place` it doesn't check the model on the route. In the `setupController`, how do I get the place_id from the url? – Chaddeus Mar 28 '13 at 14:25
  • 1
    Ah sry, haven't seen that you were relying on the params object, although i looked for it. I guess it would be possible to get the access to the param, but i would not be easy. Why do you need to make a AJAX Call again, although you pass the place? Are there more details involved in the Response? – mavilein Mar 28 '13 at 14:36
  • Ultimately I want to also query other API'd to gather more data about the place (Instagram photos for example). In my simple example, I could get away with just passing the place. While I will be building my own API to merge all the different API calls together on the server side, I wouldn't want all that data coming into the app all at once... only when the individual place is accessed. Seems like it should be a common scenario. Thanks again for the help. – Chaddeus Mar 28 '13 at 15:08
  • So this is a scenario, where you need to augment your existing data. So you could still pass the place model to your route and then access its id to get the additional data, right? – mavilein Mar 28 '13 at 16:05
  • Sure... where would I write the code to do that? I was thinking the `PlaceRoute` model hook would be the right place, but if it doesn't get called, I guess not. If a `setupController` hook doesn't have access to the `place_id` is there a better way? – Chaddeus Mar 28 '13 at 23:00
  • 1
    See my update. In setupController you also have access to the model that was passed via linkTo :-) – mavilein Mar 28 '13 at 23:09
  • 2
    This should be basic ember function, I do not know why ember creators thinks that all data should be in master (list) page - it not efficient.I thing its very confusing for all ember beginners. – petkopalko Feb 25 '14 at 11:14
  • In my case `data` is a multi array like `[{"id":"1","name":"abc"},{"id":"2","name":"def"}]`. How can I set it to the controller? – Hasib Mahmud Aug 01 '14 at 20:28
  • 1
    worth noting that if you pass an id instead of a full record to a `link-to`, the model hook will be called. – QuantumLicht Feb 29 '16 at 16:59
  • TOTALLY OUTDATED – Charney Kaye Apr 18 '17 at 21:01
17

Force Ember to use the model hook; just pass the id instead of the object:

{{#link-to 'place' place.id}}
    {{place.name}}
{{/link-to}}

As Ember no longer knows which object is related to given id, Ember will call the model hook (and so the data will be loaded from the server).

Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
  • 1
    In my opinion this should be the accepted answer, it saved me hours of fruitless debugging. My use case, probably quite a common one, is visiting a route like `/groups/:group_id`, but loading the users belonging to that group as the route's model, rather than the group itself. This is so I can paginate the users directly with ember data, as pagination of hasMany children is currently fraught with terror and danger. – omnikron Dec 03 '15 at 14:33
  • I also strongly think this should be the accepted answer, although the accepted one is correct as well. It's just that the accepted one feels more like a workaround, when this one fixes the issue in a more natural ember-ish way. – Ernesto Aug 03 '16 at 16:01
3

Based on mavilein's answer, but shorter and suitable for Ember CLI with Ember Data...

import Ember from 'ember';

export default Ember.Route.extend({
  setupController: function( controller, model ) {
    controller.set( "model", this.store.fetch( 'place', model.id ) );
  },
});

Also note that the fetch method is only available from Ember Data 1.0.0-beta.12.

http://emberjs.com/api/data/classes/DS.Store.html#method_fetch

Keith Broughton
  • 487
  • 5
  • 9