Why is it not possible to trigger the `model` hook via `link-to` helper?

hey, I’m thinking of writing a calendar app, which has the ability to change between day/week/month views for a specific user. My routes are defined like this:

App.Router.map(function () {
 this.resource('calendar', {path: '/calendar'}, function () {
    this.resource('calendar.day', {path: '/calendar/day/:user_id/:date'});
    this.resource('calendar.week', {path:'/calendar/week/:user_id/date'});
  }
});

and I want to be able to walk through the calendar by changing the date dynamic segment of the route via a link-to helper. as I would display different appointments for each user per day, I want to create my model once and query the appointments for each day. a benefit of this behaviour would be, that each day is “bookmarkable” by the user and - that’s what I like most - the user is able to use the browser’s back/forward controlls to walk through the calendar (until he comes to his entry point, of course).

so, what’s the reason why the model hook is not triggered when I change dynamic segments of the route? is there any way to mock/hack this behaviour?

OK, I did some research (mostly - frustrating - trial and error) and I could see that the Ember.Route#setupController obviously gets called when transitioning (or using the {{link-to}} helper) and the given context param from the Ember.Route#transitionTo method is passed as the model param to Ember.Route#setupController - all model related hooks are not fired.

NOTE: The context param of the Ember.Route#transitionTo method should be an Object which contains the awaited dynamic segments of the route, if there are any.

So, is it safe to say, that at least in the Ember.Route#setupController hook it is possible to determine if the user browsed to the current route by any transition or directly through updating the URL in the browser manually (or outside the application itself)? If it’s like that, I see a way to achieve the goal I want and implement the previously described scenario.

Is there any graphical abstraction of the life-cycle of the Ember.Route hooks so that one can see which hook is fired when?

If you transition to a route and all the context objects -the objects which will serve as models to templates- are passed in, the beforeModel and model hooks will not be called. I think it makes a lot of sense, if the model is already known why should the framework fetch it again? (potentially firing an XHR request). There are several ways to pass in the context objects, the link-to helper and route.transitionTo come to mind.

All the other hooks (afterModel, setupController, renderTemplate, did I miss something?) will always be fired, no matter how you transitioned to the route.

I don’t think there is a way, the setupController always gets called, with the controller and the resolved model as parameters. That said, I don’t think you need that. Transitioning between routes via link-tos does change the url and so going back and forth between them (and bookmarking) just works.

BTW, there is a much simpler way to define your routes:

App.Router.map(function () {
 this.resource('calendar', function () {
    this.route('day', {path: '/day/:user_id/:date'});
    this.route('week', {path:'/week/:user_id/:date'});
  }
});

None that I know of, and it would be good to have it. Routing is hard to grasp in Ember but is one of the key points and the more you understand it, the more you love it, at least that’s what’s happening to me :slight_smile: So don’t be discouraged, there is a lot more way to go, but the journey is worthwhile!

1 Like

hey @balint, thanks a lot for your answer - you really helped me a lot in structuring my code - now I’m working with model and afterModel hook as well as the error action to handle all transitions. sometimes I’m somewhat lost when working with Ember.js but with every issue I understand this framework more and more and I really love coding with Ember. also, thanks to such friendly and compentent users as you are :smile:

Hey @herom,

No problem. Ember.js can be hard to grasp at first, but once you get over the initial hump, it is exactly as you say it is. One loves Ember.js more with every day one spends with it.

In fact, the beforeModel hook still gets called in that case, it is only the model hook that does not. (see the async routing guide).

Sorry for the misinformation.

thanks a lot - I already read through the routing guide :smile:

Hmm, the fact this doesn’t seem to be true (anymore? I’m running 1.6.0-beta.5) is why I went searching and came across this thread. I have a resource route that can have its model set through a sidebar nav menu. By default, you land on the index route when you select a menu item, because the link-to in the nav just links to the top resource route. From there, certain buttons will take you to other nested routes. On the resource route, I use setupController to initialize some values to false that should always be false when starting off, but turn true when you visit another nested route besides index. If I visit one of those routes, and then select a different item from the menu, everything works fine; setupController fires and resets my values. But if I select the same item that I’m already on (to essentially take me back to the index route), setupController is not fired.

What your saying is probably still true but with an addendum: “matter how you transitioned to the route, unless your already there.” This should solve the problem, however.

If I understand correctly, you click a link to take you to the same route you are already on. In the case there is no transition and so nothing happens, which includes no hooks (callbacks) are fired. I think my explanation above (“no matter how you transitioned to the route”) includes that but your version might be clearer.

I was having trouble with this issue and resolved it as follows:

If an Object is passed in {{link-to}} then it will bypass the model() hook, presuming you are intending to override the model with the passed model.

However, if you would like to call the model() hook with the id of a dynamic segment, simply pass myModel.id instead.

For instance given the route: /blog/2/comments with the 2 as a dynamic segment blog/:blog_id/comments

if you call {{link-to 'blog.comments' myBlog}} it will bypass the model() hook. If you call {{link-to 'blog.comments' myBlog.id}} the hook will be called. Alternately you could call {{link-to 'blog.comments' 2}} with the same effect.

3 Likes