8

I'm following along the docs for Ember 2.3 and can't seem to find anywhere something very basic: how does one access a value provided by the route's model hook inside the main template: application.hbs?

routes/client.js

// ...
export default Ember.Route.extend({
    model() {
        return {
            navigation: [
                {
                    title: "Projects",
                    link: "projects"
                },
                {
                    title: "My Specifications",
                    link: "specs"
                }
            ]
        }
    }
});

templates/application.hbs

<nav>
   {{#each navigation as |navItem|}}
      <li>{{#link-to navItem.link}} {{navItem.title}} {{/link-to}}</li>
   {{/each}}
</nav>
{{outlet}}

As it is now, the navigation object is accessible to the route's template (client.hbs) but not to the application template.

nem035
  • 34,790
  • 6
  • 87
  • 99
Slavic
  • 1,891
  • 2
  • 16
  • 27
  • 1
    First of all you should reffer to model data using {{#each model.navigation ...}} in the template. Secondly, each route is corresponding to its template. In your case you need application route to display data from model in application hbs. – kristjan reinhold Feb 04 '16 at 07:13
  • That is precisely what I am not interested in, @kristjan. I have a (very common) situation where I need an usual route to provide data that is to be accessed inside the parent template (application.hbs). For example the list of active navigation. – Slavic Feb 04 '16 at 09:13
  • @Slavic Each template having its own route is the ember way. Convention over configuration, but if you want to display the navigation model in your application route why not return multiple models for your application route? – Craicerjack Feb 04 '16 at 09:35
  • @Craicerjack Assuming I want to follow ember's way, how do I address the problem at hand? The problem is still there: application.hbs does not even have a router of its own and even if it did, its router wouldn't know the data, because, by the problem's definition, the data is specific to the subroutes. – Slavic Feb 04 '16 at 09:39
  • Could you provide some additional text what are you trying to achieve. Maybe there are better ways to solve your issue and your looking at the wrong angle to it. Because it does not make any sense to me what you're currently doing. – kristjan reinhold Feb 04 '16 at 09:51
  • What I'm trying to achieve is just this: access data provided by a route inside the main application template. As it looks from the documentation, the only data the application template has its hands on, is the data provided by the application controller/route. – Slavic Feb 04 '16 at 09:58
  • @Slavic from what I remember of Ember if you dont create the route yourself ember does it automatically in the background so application.hbs does have a route, but right now its just a generic one that ember created. Other than that without more context its hard to know. You can load multiple models in a route so you could create your own application route and load the models you need in that. See [this question](http://stackoverflow.com/questions/20521967/emberjs-how-to-load-multiple-models-on-the-same-route). That being said I havent used Ember in over a year so things might have changed. – Craicerjack Feb 04 '16 at 10:42
  • Again, the idea is: not one single route/controller will know the data. The data is supposed to be sent from any one "active" route at the moment. For example: /library will return "read" and "review" menus, while /disco will return "book" and "see photos" menus. – Slavic Feb 04 '16 at 10:46

4 Answers4

4

Here's how it's done (unless ember comes up with a better way in future releases):

routes/client.js

// ...
export default Ember.Route.extend({
    setupController() {
        this.controllerFor('application').set('navigation', ["nav1", "nav2"]);
    }
});

Thanks, Ivan, for the answer!

Slavic
  • 1,891
  • 2
  • 16
  • 27
  • The focus wasn't the right place for controllerFor method, but, yes - I agree this is not the best place – Slavic Feb 04 '16 at 12:24
2

How does one access a value provided by the route's model hook inside the main template

By default, inside the setupController hook of the route, Ember will set the property model on your controller to the resolved value of the promise returned from the model hook of the route.

Meaning you can just use the property model in your template to access model.navigation:

<nav>
   {{#each model.navigation as |navItem|}}
      <li>{{#link-to navItem.link}} {{navItem.title}} {{/link-to}}</li>
   {{/each}}
</nav>

If you want to use a different name, you can override the setupController hook and set the name yourself:

export default Ember.Route.extend({
  // ... 
  setupController(controller, model) {
    this.set('navigation', Ember.get(model, 'navigation'));
  }
  // ... rest of the code
})

Which means you can now use navigation instead of model.navigation inside you template. Another way could be to add an alias inside your controller for the model property:

export default Ember.Controller.extend({
  navigation: Ember.computed.alias('model.navigation')
  // ... rest of the code
})

Which will allow you as well to use navigation instead of model.navigation.

However, if you want to have some sort of global navigation in your application, the better way would be to use a Service which you would inject into any controller that needs navigation. Something like:

// app/services/navigation.js
export default Ember.Service.extend({
  items: null,

  init() {
    this._super(...arguments);
    this.set('items', [{
      title: "Projects",
      link: "projects"
    }, {
      title: "My Specifications",
      link: "specs"
    }]);
  }
});

And then inject it in your controllers:

export default Ember.Controller.extend({
    navigation: Ember.service.inject()
});

And now you have access to navigation in that controller's template as well.

nem035
  • 34,790
  • 6
  • 87
  • 99
  • I wasn't concerned with accessing the data using another name. To me model.navigation is perfectly ok. The task was to just get a hold of that data. – Slavic Feb 04 '16 at 12:27
  • @Slavic ok, I see how I missunderstood you, I corrected my answer. I also added a suggestion on how to implement more global navigation, if necessary. – nem035 Feb 05 '16 at 05:27
  • Unfortunately, a service does not seem to address the problem at hand. It stems from the fact that each controller will have its say towards the navigation (for example to mark a nav item as active, but not just this). Right now I'm leaning towards the setupController solution, and although I don't yet know if it will solve all my navigation problems, it certainly is a solution to what has been asked, specifically. You have my upvote. – Slavic Feb 05 '16 at 09:26
0

I would resolve this issue like this. Create a navigation component which accepts custom parameters.

Templates application.hbs

{{custom-navigation url=apiURL user=user}}

Now in your client.js

apiURL: 'navigations/client'

And your custom component custom-navigation.js

resolvedRouteNavs: function() {
   return DS.PromiseArray.create({
            promise: store.query(this.get('url'), { user? : userID?? });
   })
}.property('apiURL')

And custom-navigation.hbs.

{{#each resolvedRouteNavs as |navItem|}}
  {{navItem.name}}
{{/each}}

If you're dealing with static arrays as navigations

then no resolving is needed and would be simply to out put the binded array, which is different for each route.

client.js

navs: [{ name: '1', route: 'foo'}, { name: '2', route: 'bar' }]

some-different-place.js

navs: [{ name: 'blah', route: 'foo.bar'}, { name: 'meh', route: 'bar.foo' }]

And really model hook should contain data that is retrieved from server. Otherwise use setupController hook.

setupController: function(controller, model) {
        this._super(controller, model);

        controller.setProperties({
            nav: []
        });
kristjan reinhold
  • 2,038
  • 1
  • 17
  • 34
0

I recently needed to solve this problem and this is how I did it.

First, this is what I have in my application.hbs template:

`    <!-- app/templates/application.hbs -->
<div class="page">
     {{!-- Main site navigation menu --}}
     {{app-navbar isHidden=hidesNavbar}}
         {{!-- Site content overrides this block --}}
         {{outlet}}
     {{!-- Main site footer --}}
     {{app-footer isHidden=hidesFooter}}
</div>`

In my app, app-navbar and app-footer are both components that know how to use the isHidden property to either make themselves visible or invisible. There are different ways of toggling visibility but for the sake of brevity, let's just wrap the contents of either component inside a conditional block like so:

`<!-- app-navbar and app-footer -->
{{#unless isHidden}} 
     <!-- component HTML here --> 
{{/unless}}
`

Now, from the route where I don't want to see either app-navbar or app-footer, in my case this is login.js, I can call setupController() and toggle the hidesNavbar and hidesFooter properties of the application controller. Looks something like this:

`// app/routes/login.js
import Route from '@ember/routing/route';
export default Route.extend({
    setupController() {
      this.controllerFor('application').set('hidesNavbar', true);
      this.controllerFor('application').set('hidesFooter', true);
    }
});`

Now, whenever we transition to the login route the hidesNavbar and hidesFooter properties get set on the application controller making the isHidden properties in the app-navbar and app-footer both true.

Leo
  • 449
  • 6
  • 5