setupController no longer working for Ember route - ember.js

I can not longer setup a route that has a hash of multiple models. What I use to do is this:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
products: this.store.findAll('product'),
suppliers: this.store.findAll('supplier')
});
},
setupController(controller, model) {
controller.set('products', model.products);
controller.set('suppliers', model.suppliers);
}
});
When I do this I can't loop through each {{#each products}} but I still can with {{#each model.products}}

Object proxying is deprecated, so you should refer to your models with a model. prefix.
The correct way to do this would be to leave out your setupController and use the model as an object with .products and .suppliers keys on it.
You'll still have to {{#each model.products as |product|}} unless you want to alias them in your controller like this:
products: Ember.computed.alias("model.products")
I'd recommend sticking with model.products in your template though to alleviate any confusion as to where that particular data came from. Removing it would imply it's controller-related vs. model data fetched in the route's model hook.
EDIT: The above solution was done in Ember 1.13.5 and should also work in any 2.x version.

Related

Ember.js: Iterating through multiple models set on controller

I want to declare two models to be loaded by my controller.
I am not using the model,beforeModel, or afterModel hooks because:
I don't have to implement loading routes/templates
I can load each model independently in the page (like lazy loading)
I am using the setupController hook in my route as so:
setupController: function(controller, model) {
var that = this;
controller.set('model', {
stations: that.store.find('station'),
packages: that.store.find('package'),
});
}
My controller is an Ember.Controller.
In order to iterate through these models in Handlebars I have to use model.stations.content.content which makes me think I am doing something wrong:
{{#each station in model.stations.content.content}}
// .....
{{/each}}
Is there a better way to set these models on the controller or use a different controller type or something?
#MilkyWayJoe's method is definitely the right way to do it if you realize that if you have a large model or your AJAX requests take a long time, your UI will go blank and Ember will try to find a loading process while the model is loading.
#Chaosekie suggested the answer to my question which was to wait for the promise to load the model from the store to fulfill then set the property on the controller.
I.e. in the route:
setupController: function(controller, model) {
this.store.find('station').then(function(stations) {
controller.set('stations',stations);
});
this.store.find('package').then(function(packages) {
controller.set('packages', packages);
});
}
This allows one to iterate through both models using the 'normal' way in Handlebars:
{{#each package in packages}}
// ....
{{/each}}
{{#each station in stations}}
// ....
{{/each}}
You can change your model to return Ember.RSVP.hash which will act like a promise bag, and within the scope of the hash you fill up the attributes stations and packages with your data, so when the model gets resolved it will set that hash schema as the model in your controller:
[...]
model: function() {
return Em.RSVP.hash({
stations: this.store.find('station'),
packages: this.store.find('package')
});
}
[...]
Then in your template:
{{#each item in model.stations}}
<li>{{item.name}}</li>
{{/each}}
or
{{#each item in model.packages}}
<li>{{item.name}}</li>
{{/each}}
(See jsbin)
You do not need to create a new object for your model.
You could do the following in your controller:
setupController: function(controller, model){
controller.set('model', model);
this.stations().then(function(value){
controller.set('stations', value);
});
}
Where stations is:
stations: function() {
return this.store.find('station');
}
Then you can use stations in your template in the same way as you would use model. Check out this example
But a controller should really only decorate one model, you might consider creating nested routes ( which will give you nested controllers ).

Ember model proxying deprecation confusion

I'm trying to work through deprecations messages in Ember 1.11 about object proxying in templates, and have run into confusion.
On an edit route, for example, I was loading a single record in the model hook. Then, using the setupController hoook, I was setting other collection variables to enable select menus. This provoked deprecation notices about proxying; I attempted to resolve those by setting all data in the model hook using an Ember.RSVP.hash as the model.
I now have a problem where, if I load the page directly, the model is correct, but if I visit from a link-to, the model is incorrect. I found information on similar problems that led me to try using setupController to set the model, based on this.get('model'), hoping that would grab the model from the route's model funtion, but this also did not work. The setupController action is being invoked, but it doesn't seem to be affecting the template for some reason, which is where I've had a roadblock.
show.hbs:
{{link-to model}}
edit.hbs –– data present if direct page load, empty if visiting from show link-to
{{model.foo.name}}
edit route:
renderTemplate: function (controller,model) {
this.render('foo/form');
},
model: function (project) {
return Ember.RSVP.hash({
foo: this.store.find("foo", foo.foo_id),
bar: this.store.find('bar')
});
}
also tried this for edit route:
renderTemplate: function (controller,model) {
this.render('foo/form');
},
model: function (project) {
return Ember.RSVP.hash({
foo: this.store.find("foo", foo.foo_id),
bar: this.store.find('bar')
});
},
setupController: function (controller, model) {
controller.set('model', this.get('model');
}
I've found one simple way around this, not sure if there is a better "Ember way" –– I see that because link-to is passing the model, it is assumed that there is no model hook required; passing model.id instead of model as link-to argument does cause the model hook to be invoked.
{{link-to model.id}}
instead of:
{{link-to model}}

Why is Firebase data not displaying properly in my Ember CLI generated output?

I've successfully setup Ember CLI and Firebase and I'm attempting to bring some basic data into my templates. My 'title' and 'subtitle' data are apparent in the Ember Inspector, as well as my Firebase project dashboard. However, {{foo.title}} and {{foo.subtitle}} are coming back empty and undefined in the browser. Why is that? Here's my code:
application.js (adapter)
import DS from 'ember-data';
export default DS.FirebaseAdapter.extend({
firebase: new window.Firebase('https://<firebase-database-name>.firebaseio.com/')
});
foo.js (model)
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
subtitle: DS.attr('string')
});
index.js (controller)
import Ember from 'ember';
export default Ember.Controller.extend({
model: function() {
var titles = this.store.createRecord('foo', {
title: 'Title',
subtitle: 'Subtitle'
});
titles.save();
}
});
index.js (route)
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
});
application.hbs (template)
<h2 id='title'>{{foo.title}}</h2>
{{outlet}}
index.hbs (template)
<h1>{{foo.title}}</h1>
<h3>{{foo.subtitle}}</h3>
The title and subtitle fail to display in the templates.
The Ember Inspector View Tree tab shows 'index' with 'DS.RecordArray:ember368' for the model.
The Ember Inspector Data tab shows Model Type of 'foo' with # Records of 1. When I click on that record, it displays the Firebase ID, title, and subtitle values. When I inspect my Firebase data url, I see the following structure:
firebase-database-name
|— foos
|— JU1Ay8emCNNZBeqYoda
|— subtitle: "Subtitle"
|— title: "Title"
Seems like everything is correct, but the templates do not display the data values. Thanks for any help.
The answer to this question centers on properly retrieving and exposing Ember Data, and not so much to do with Firebase or Ember CLI. There are multiple issues with the code above…
The foo.js code represents a simple model, and is written correctly.
The index.js route is implemented correctly. It is retrieving and returning the ‘foo’ model from the Ember Data store as an array, which, via EmberFire and the Firebase adapter, is ultimately being pulled from the Firebase database. However, this is part 1 of 3 problems. If you want this data displayed once across the application, dispense with the index.js route, and just define an application.js route, like this:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.findAll('foo');
}
}
The index.js controller has a number of issues, and is part 2 of 3 problems. Firstly, controllers do not have a ‘model’ method, they only have a ‘model’ property (Ember Routes are the ones that employ a ‘model’ method, and can also set the ‘model’ property of a controller via a Route’s ‘setupController’ method). Secondly, instead of Ember.Controller, it needs to extend Ember.ObjectController for a singular data instance, or, Ember.ArrayController for an array of data, which is the controller needed here, since ‘this.store.findAll(“foo”)’ in the index.js route is going to return an array of objects. Controllers are not used to save or retrieve data from a server, but they can be used to decorate a model. Given that the route is returning the model, the controller, in this simple data exercise, is not even necessary.
The application.hbs handlebars template is part 3 of 3 problems. It is not setup to properly display the model that is being provided to it via the route. It’s necessary to employ the {{#each}} helper, to loop over the data array that is being returned via the route’s model method. Try this:
{{!-- looping over the 'foo' model returned via the route --}}
{{#each foo in model}}
<h2>Application Title = <span style="color: blue;">{{foo.title}}</span></h2>
<h4>Application Tagline = <span style="color: blue;">{{foo.tagline}}</span></h4>
{{/each}}
{{outlet}}
The index.hbs handlebars template is not necessary. The application.hbs template is sufficient to display the data of interest.
This is a very basic exercise, but illustrates fundamental aspects of using Ember Data properly.

Ember, Ember-data, and jquery-ui.dialog, "Oh my!"

The task:
Open a form in a lightbox to create a new "event"; the opened form should be bookmarkable.
The road blocks:
There are examples of opening a lightbox using {{action}} tags, but could not find one that opened in its own route.
There are many examples using older versions of ember.js.
There is not a lot of documentation related to ember-data and REST (I know, I know...it isn't "production ready").
The problem:
The fields in the form were not being tied to a backing model so "null" was being posted to my servlet (a Spring controller).
My very first iteration was not too far off from the final outcome (jsfiddle). The thing that finally made it works swapping this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
controller.set("model", model);
},
...
});
...for this:
EP.EventsNewRoute = Ember.Route.extend({
...
setupController : function(controller, model) {
this.controllerFor("events-new").set("model", model);
},
...
});
The question:
Why does the setupController function need to call controllerFor in order to properly set up the model?
And finally, since I struggled to find a fully-functional example, I wanted to make this accessible (and hopefully discover improvements).
Here's the fiddle: http://jsfiddle.net/6thJ4/1/
Here are a few snippets.
HTML:
<script type="text/x-handlebars">
<div>
<ul>
{{#linkTo "events.new" tagName="li"}}
Add Event
{{/linkTo}}
</ul>
</div>
{{outlet events-new}}
</script>
<script type="text/x-handlebars" data-template-name="events-new">
<form>
<div>
<label>Event Name:</label>
{{view Ember.TextField valueBinding="name"}}
</div>
<div>
<label>Host Name:</label>
{{view Ember.TextField valueBinding="hostName"}}
</div>
</form>
</script>
JavaScript:
...
EP.Router.map(function() {
this.resource("events", function() {
this.route("new");
});
});
EP.EventsNewRoute = Ember.Route.extend({
model : function() {
return EP.Event.createRecord();
},
setupController : function(controller, model) {
//controller.set("model", model); // Doesn't work? Why not?
this.controllerFor("events-new").set("model", model); // What does this do differently?
},
...
});
EP.EventsNewController = Ember.ObjectController.extend({
save : function() {
this.get("content.transaction").commit(); // "content.store" would commit _everything modified_, we only have one element changed, so only "content.transaction" is necessary.
}
});
EP.EventsNewView = Ember.View.extend({
...
});
EP.Event = DS.Model.extend({
name : DS.attr("string"),
hostName : DS.attr("string")
});
Resources:
http://emberjs.com/guides/routing/setting-up-a-controller/
http://emberjs.com/guides/getting-started/toggle-all-todos/ (trying to mimic what I learned, but morph the add-new to a new route)
Writing a LightboxView causes problems / Integrating DOM Manipulating jQuery-Plugins makes actions unusable (lightbox "example")
Dependable views in Ember (another lightbox "example" but doesn't have routes for the lightbox opening)
Why does the setupController function need to call controllerFor in order to properly set up the model?
Ember makes URLs a very integral part of its conventions. This means that the state of your application is represented by the route it is on. You've grokked most of this correctly. But there are couple of subtle nuances, that I will clarify below.
First consider an app with the following URLs,
/posts - shows a list of blog posts.
/posts/1 - shows a single blog post.
And say clicking on a post in the list at /posts takes you to /posts/1.
Given this scenario, there 2 ways a user will get to see the post at /posts/1.
By going to /posts and clicking on the 1st post.
By typing in /posts/1, via bookmarks etc.
In both these cases, the PostRoute for /posts/1 will need the model corresponding to Post id 1.
Consider the direct typing scenario first. To provide a way to lookup the id=1 post model, you would use,
model: function(params) {
return App.Post.find(params.post_id);
}
Your template for post will get the model and it can render using it's properties.
Now consider the second scenario. Clicking on post with id=1 takes you to /posts/1. To do this your template would use linkTo like this.
{{#linkTo 'post' post}} {{post.title}} {{/linkTo}}
Here you are passing in the post model to the linkTo helper. It then serializes the data for the post into a URL, ie:- '/posts/1'. When you click on this link Ember realizes that it needs to render the PostRoute but it already has the post model. So it skips the model hook and directly calls setupController.
The default setupController is setup to simply assign the model on the controller. It's implemented to do something like,
setupController: function(controller, model) {
controller.set('model', model);
}
If you do not need to set custom properties on your controller, you don't need to override it. Note: if you are augmenting it with additional properties you still need to call _super to ensure that the default setupController behaviour executes.
setupController: function(controller, model) {
this._super.apply(this, arguments);
controller.set('customProp', 'foo');
}
One final caveat, If you are using linkTo and the route does not have dynamic segments, then the model hook is still called. This exception makes sense if you consider that you were linking to the /posts route. Then the model hook has to fire else Ember has no data to display the route.
Which brings us to the crux of your question. Nearly there, I promise!
In your example you are using linkTo to get to the EventsNewRoute. Further your EventsNewRoute does not have dynamic segments so Ember does call the model hook. And controller.set("model", model); does work in so much as setting the model on the controller.
The issue is to do with your use of renderTemplate. When you use render or {{render}} helper inside a template, you are effectively getting a different controller to the one you are using. This controller is different from the one you set the model on, hence the bug.
A workaround is to pass the controller in the options, which is why renderTemplate gets this controller as an argument.
renderTemplate: function(controller) {
this.render("events-new", {
outlet : "events-new", controller: controller
});
}
Here's an updated jsfiddle.
Final Note: Unrelated to this question, you are getting the warning,
WARNING: The immediate parent route ('application') did not render into the main outlet and the default 'into' option ('events') may not be expected
For that you need to read this answer. Warning, it's another wall of text! :)

How to use multiple models with a single route in EmberJS / Ember Data?

From reading the docs, it looks like you have to (or should) assign a model to a route like so:
App.PostRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
What if I need to use several objects in a certain route? i.e. Posts, Comments and Users? How do I tell the route to load those?
Last update forever: I can't keep updating this. So this is deprecated and will likely be this way. here's a better, and more up-to-date thread EmberJS: How to load multiple models on the same route?
Update: In my original answer I said to use embedded: true in the model definition. That's incorrect. In revision 12, Ember-Data expects foreign keys to be defined with a suffix (link) _id for single record or _ids for collection. Something similar to the following:
{
id: 1,
title: 'string',
body: 'string string string string...',
author_id: 1,
comment_ids: [1, 2, 3, 6],
tag_ids: [3,4]
}
I have updated the fiddle and will do so again if anything changes or if I find more issues with the code provided in this answer.
Answer with related models:
For the scenario you are describing, I would rely on associations between models (setting embedded: true) and only load the Post model in that route, considering I can define a DS.hasMany association for the Comment model and DS.belongsTo association for the User in both the Comment and Post models. Something like this:
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
email: DS.attr('string'),
posts: DS.hasMany('App.Post'),
comments: DS.hasMany('App.Comment')
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
body: DS.attr('string'),
author: DS.belongsTo('App.User'),
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
body: DS.attr('string'),
post: DS.belongsTo('App.Post'),
author: DS.belongsTo('App.User')
});
This definition would produce something like the following:
With this definition, whenever I find a Post, I will have access to a collection of comments associated with that post, and the comment's author as well, and the user which is the author of the post, since they are all embedded. The route stays simple:
App.PostsPostRoute = Em.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
}
});
So in the PostRoute (or PostsPostRoute if you're using resource), my templates will have access to the controller's content, which is the Post model, so I can refer to the author, simply as author
<script type="text/x-handlebars" data-template-name="posts/post">
<h3>{{title}}</h3>
<div>by {{author.fullName}}</div><hr />
<div>
{{body}}
</div>
{{partial comments}}
</script>
<script type="text/x-handlebars" data-template-name="_comments">
<h5>Comments</h5>
{{#each content.comments}}
<hr />
<span>
{{this.body}}<br />
<small>by {{this.author.fullName}}</small>
</span>
{{/each}}
</script>
(see fiddle)
Answer with non-related models:
However, if your scenario is a little more complex than what you described, and/or have to use (or query) different models for a particular route, I would recommend to do it in Route#setupController. For example:
App.PostsPostRoute = Em.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
},
// in this sample, "model" is an instance of "Post"
// coming from the model hook above
setupController: function(controller, model) {
controller.set('content', model);
// the "user_id" parameter can come from a global variable for example
// or you can implement in another way. This is generally where you
// setup your controller properties and models, or even other models
// that can be used in your route's template
controller.set('user', App.User.find(window.user_id));
}
});
And now when I'm in the Post route, my templates will have access to the user property in the controller as it was set up in setupController hook:
<script type="text/x-handlebars" data-template-name="posts/post">
<h3>{{title}}</h3>
<div>by {{controller.user.fullName}}</div><hr />
<div>
{{body}}
</div>
{{partial comments}}
</script>
<script type="text/x-handlebars" data-template-name="_comments">
<h5>Comments</h5>
{{#each content.comments}}
<hr />
<span>
{{this.body}}<br />
<small>by {{this.author.fullName}}</small>
</span>
{{/each}}
</script>
(see fiddle)
Using Em.Object to encapsulate multiple models is a good way to get all data in model hook. But it can't ensure all data is prepared after view rendering.
Another choice is to use Em.RSVP.hash. It combines several promises together and return a new promise. The new promise if resolved after all the promises are resolved. And setupController is not called until the promise is resolved or rejected.
App.PostRoute = Em.Route.extend({
model: function(params) {
return Em.RSVP.hash({
post: // promise to get post
comments: // promise to get comments,
user: // promise to get user
});
},
setupController: function(controller, model) {
// You can use model.post to get post, etc
// Since the model is a plain object you can just use setProperties
controller.setProperties(model);
}
});
In this way you get all models before view rendering. And using Em.Object doesn't have this advantage.
Another advantage is you can combine promise and non-promise. Like this:
Em.RSVP.hash({
post: // non-promise object
user: // promise object
});
Check this to learn more about Em.RSVP: https://github.com/tildeio/rsvp.js
But don't use Em.Object or Em.RSVP solution if your route has dynamic segments
The main problem is link-to. If you change url by click link generated by link-to with models, the model is passed directly to that route.
In this case the model hook is not called and in setupController you get the model link-to give you.
An example is like this:
The route code:
App.Router.map(function() {
this.route('/post/:post_id');
});
App.PostRoute = Em.Route.extend({
model: function(params) {
return Em.RSVP.hash({
post: App.Post.find(params.post_id),
user: // use whatever to get user object
});
},
setupController: function(controller, model) {
// Guess what the model is in this case?
console.log(model);
}
});
And link-to code, the post is a model:
{{#link-to "post" post}}Some post{{/link-to}}
Things become interesting here. When you use url /post/1 to visit the page, the model hook is called, and setupController gets the plain object when promise resolved.
But if you visit the page by click link-to link, it passes post model to PostRoute and the route will ignore model hook. In this case setupController will get the post model, of course you can not get user.
So make sure you don't use them in routes with dynamic segments.
For a while I was using Em.RSVP.hash, however the problem I ran into was that I didn't want my view to wait until all models were loaded before rendering. However, I found a great (but relatively unknown) solution thanks to the folks at Novelys that involves making use of the Ember.PromiseProxyMixin:
Let's say you have a view that has three distinct visual sections. Each of these sections should be backed by its own model. The model backing the "splash" content at the top of the view is small and will load quickly, so you can load that one normally:
Create a route main-page.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('main-stuff');
}
});
Then you can create a corresponding Handlebars template main-page.hbs:
<h1>My awesome page!</h1>
<ul>
{{#each thing in model}}
<li>{{thing.name}} is really cool.</li>
{{/each}}
</ul>
<section>
<h1>Reasons I Love Cheese</h1>
</section>
<section>
<h1>Reasons I Hate Cheese</h1>
</section>
So let's say in your template you want to have separate sections about your love/hate relationship with cheese, each (for some reason) backed by its own model. You have many records in each model with extensive details relating to each reason, however you'd like the content on top to render quickly. This is where the {{render}} helper comes in. You can update your template as so:
<h1>My awesome page!</h1>
<ul>
{{#each thing in model}}
<li>{{thing.name}} is really cool.</li>
{{/each}}
</ul>
<section>
<h1>Reasons I Love Cheese</h1>
{{render 'love-cheese'}}
</section>
<section>
<h1>Reasons I Hate Cheese</h1>
{{render 'hate-cheese'}}
</section>
Now you'll need to create controllers and templates for each. Since they're effectively identical for this example, I'll just use one.
Create a controller called love-cheese.js:
import Ember from 'ember';
export default Ember.ObjectController.extend(Ember.PromiseProxyMixin, {
init: function() {
this._super();
var promise = this.store.find('love-cheese');
if (promise) {
return this.set('promise', promise);
}
}
});
You'll notice that we are using the PromiseProxyMixin here, which makes the controller promise-aware. When the controller is initialized, we indicate that the promise should be loading the love-cheese model via Ember Data. You'll need to set this property on the controller's promise property.
Now, create a template called love-cheese.hbs:
{{#if isPending}}
<p>Loading...</p>
{{else}}
{{#each item in promise._result }}
<p>{{item.reason}}</p>
{{/each}}
{{/if}}
In your template, you'll be able to render different content depending on the state of promise. When your page initially loads, your "Reasons I Love Cheese" section will display Loading.... When the promise is loaded, it will render all the reasons associated for each record of your model.
Each section will load independently and not block the main content from rendering immediately.
This is a simplistic example, but I hope everyone else finds it as useful as I did.
If you're looking to do something similar for many rows of content, you may find the Novelys example above even more relevant. If not, the above should work fine for you.
This might not be best practice and a naïve approach, but it shows conceptually how you would go about having on multiple models available on one central route:
App.PostRoute = Ember.Route.extend({
model: function() {
var multimodel = Ember.Object.create(
{
posts: App.Post.find(),
comments: App.Comments.find(),
whatever: App.WhatEver.find()
});
return multiModel;
},
setupController: function(controller, model) {
// now you have here model.posts, model.comments, etc.
// as promises, so you can do stuff like
controller.set('contentA', model.posts);
controller.set('contentB', model.comments);
// or ...
this.controllerFor('whatEver').set('content', model.whatever);
}
});
hope it helps
Thanks to all the other excellent answers, I created a mixin that combines the best solutions here into a simple and reusable interface. It executes an Ember.RSVP.hash in afterModel for the models you specify, then injects the properties into the controller in setupController. It does not interfere with the standard model hook, so you would still define that as normal.
Example use:
App.PostRoute = Ember.Route.extend(App.AdditionalRouteModelsMixin, {
// define your model hook normally
model: function(params) {
return this.store.find('post', params.post_id);
},
// now define your other models as a hash of property names to inject onto the controller
additionalModels: function() {
return {
users: this.store.find('user'),
comments: this.store.find('comment')
}
}
});
Here is the mixin:
App.AdditionalRouteModelsMixin = Ember.Mixin.create({
// the main hook: override to return a hash of models to set on the controller
additionalModels: function(model, transition, queryParams) {},
// returns a promise that will resolve once all additional models have resolved
initializeAdditionalModels: function(model, transition, queryParams) {
var models, promise;
models = this.additionalModels(model, transition, queryParams);
if (models) {
promise = Ember.RSVP.hash(models);
this.set('_additionalModelsPromise', promise);
return promise;
}
},
// copies the resolved properties onto the controller
setupControllerAdditionalModels: function(controller) {
var modelsPromise;
modelsPromise = this.get('_additionalModelsPromise');
if (modelsPromise) {
modelsPromise.then(function(hash) {
controller.setProperties(hash);
});
}
},
// hook to resolve the additional models -- blocks until resolved
afterModel: function(model, transition, queryParams) {
return this.initializeAdditionalModels(model, transition, queryParams);
},
// hook to copy the models onto the controller
setupController: function(controller, model) {
this._super(controller, model);
this.setupControllerAdditionalModels(controller);
}
});
https://stackoverflow.com/a/16466427/2637573 is fine for related models. However, with recent version of Ember CLI and Ember Data, there is a simpler approach for unrelated models:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseArray.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2)
});
}
});
If you only want to retrieve an object's property for model2, use DS.PromiseObject instead of DS.PromiseArray:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller,model);
var model2 = DS.PromiseObject.create({
promise: this.store.find('model2')
});
model2.then(function() {
controller.set('model2', model2.get('value'))
});
}
});
Adding to MilkyWayJoe's answer, thanks btw:
this.store.find('post',1)
returns
{
id: 1,
title: 'string',
body: 'string string string string...',
author_id: 1,
comment_ids: [1, 2, 3, 6],
tag_ids: [3,4]
};
author would be
{
id: 1,
firstName: 'Joe',
lastName: 'Way',
email: 'MilkyWayJoe#example.com',
points: 6181,
post_ids: [1,2,3,...,n],
comment_ids: [1,2,3,...,n],
}
comments
{
id:1,
author_id:1,
body:'some words and stuff...',
post_id:1,
}
...
I believe the link backs are important so that the full relationship is established. Hope that helps someone.
You could use the beforeModel or afterModel hooks as these are always called, even if model is not called because you're using dynamic segments.
As per the asynchronous routing docs:
The model hook covers many use cases for pause-on-promise transitions, but sometimes you'll need the help of the related hooks beforeModel and afterModel. The most common reason for this is that if you're transitioning into a route with a dynamic URL segment via {{link-to}} or transitionTo (as opposed to a transition caused by a URL change), the model for the route you're transitioning into will have already been specified (e.g. {{#link-to 'article' article}} or this.transitionTo('article', article)), in which case the model hook won't get called. In these cases, you'll need to make use of either the beforeModel or afterModel hook to house any logic while the router is still gathering all of the route's models to perform a transition.
So say you have a themes property on your SiteController, you could have something like this:
themes: null,
afterModel: function(site, transition) {
return this.store.find('themes').then(function(result) {
this.set('themes', result.content);
}.bind(this));
},
setupController: function(controller, model) {
controller.set('model', model);
controller.set('themes', this.get('themes'));
}