如何在EmberJS / Ember Data中使用单个path的多个模型?

从阅读文档看来,您必须(或者应该)将模型分配给路由,如下所示:

App.PostRoute = Ember.Route.extend({ model: function() { return App.Post.find(); } }); 

如果我需要在某个path中使用多个对象呢? 即post,评论和用户? 我如何告诉路线加载这些?

永远最后一次更新 :我无法继续更新。 所以这是不赞成的,可能会这样。 这里有一个更好的和更新的线程EmberJS:如何在同一条路线上加载多个模型?

更新:在我原来的答案我说在模型定义中使用embedded: true 。 这是不正确的。 在版本12中,Ember-Data期望外键被定义为单个logging的后缀( 链接 ) _id或收集的_ids 。 类似于以下内容:

 { id: 1, title: 'string', body: 'string string string string...', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] } 

我已经更新了小提琴,并会再次这样做,如果有任何改变,或者如果我发现在这个答案中提供的代码更多的问题。


回答相关模型:

对于您描述的场景,我将依赖模型之间的关联 (设置embedded: true ),并仅在该路线中加载Post模型,考虑到我可以为Comment模型定义一个DS.hasMany关联,并为DS.belongsTo关联定义CommentPost模型中的用户。 像这样的东西:

 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') }); 

这个定义会产生如下内容:

模型之间的关联

有了这个定义,每当我find一个post时,我都可以访问与该post相关的评论集合,评论的作者以及post作者的用户, 因为它们都是embedded的 。 路线保持简单:

 App.PostsPostRoute = Em.Route.extend({ model: function(params) { return App.Post.find(params.post_id); } }); 

因此,在PostRoute (或PostsPostRoute如果您使用resource ),我的模板将有权访问控制器的content ,这是Post模型,所以我可以参考作者,简单地作为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> 

(见小提琴 )


回答非相关模型:

但是,如果你的场景比你描述的要复杂一点,并且/或者不得不为特定的路线使用(或者查询)不同的模型,我build议在Route#setupController 。 例如:

 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)); } }); 

现在,当我在Postpath中时,我的模板将可以访问控制器中的user属性,因为它是在setupController钩子中设置的:

 <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> 

(见小提琴 )

使用Em.Object封装多个模型是获取model钩子中所有数据的好方法。 但是它不能保证视图渲染后所有的数据都准备好了。

另一个select是使用Em.RSVP.hash 。 它将几个承诺结合在一起,并返回一个新的承诺。 所有的承诺解决后,如果解决了新的承诺。 直到promise被parsing或拒绝, setupController才会被调用。

 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); } }); 

通过这种方式,您可以在查看渲染前获取所有模型 而使用Em.Object没有这个优点。

另一个优点是可以结合承诺和不承诺。 喜欢这个:

 Em.RSVP.hash({ post: // non-promise object user: // promise object }); 

检查这个了解更多关于Em.RSVP


但是,如果您的路线具有dynamic细分,请不要使用Em.ObjectEm.RSVP解决scheme

主要的问题是link-to 。 如果通过点击链接link-to模型生成的link-to更改url,模型将直接传递到该路线。 在这种情况下, model钩子不会被调用,并且在setupController您将获得模型link-to

一个例子是这样的:

路线代码:

 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); } }); 

link-to代码,这个post是一个模型:

 {{#link-to "post" post}}Some post{{/link-to}} 

事情在这里变得有趣。 当你使用url /post/1来访问页面时, model钩子被调用,并且当promise被parsing时, setupController获取普通对象。

但是,如果您通过单击link-to链接访问该页面,则会将post模型传递到PostRoute ,并且路由将忽略model挂钩。 在这种情况下, setupController将获得post模型,当然你不能得到用户。

因此,请确保您不要在具有dynamic细分的路线中使用它们。

有一段时间我在使用Em.RSVP.hash ,但是我碰到的问题是,我不希望我的视图等到所有的模型在渲染之前加载。 然而,我发现一个伟大的(但相对未知)的解决scheme,感谢Novelys的人们使用Ember.PromiseProxyMixin :

假设您有一个视图有三个截然不同的视觉部分。 每个部分都应该有自己的模型。 支持视图顶部的“闪屏”内容的模型很小,并且会很快加载,所以您可以正常加载:

创build一个路线main-page.js

 import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.store.find('main-stuff'); } }); 

然后你可以创build一个相应的Handlebars模板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> 

所以,让我们来说一下,在你的模板中,你想分开的部分关于你与奶酪的爱/恨关系,每个部分(出于某种原因)都由自己的模型支持。 每个模型中有很多logging,每个原因都有详细的详细信息,但是您希望顶部的内容能够快速呈现。 这是{{render}}帮助程序的来源。您可以更新您的模板,如下所示:

 <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> 

现在您需要为每个控制器和模板创build。 由于它们在这个例子中是完全相同的,我只用一个。

创build一个名为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); } } }); 

你会注意到我们在这里使用了PromiseProxyMixin ,这使得控制器可以承诺。 当控制器被初始化时,我们指示承诺应该通过Ember Data加载love-cheese模型。 你需要在控制器的promise属性上设置这个属性。

现在,创build一个名为love-cheese.hbs的模板:

 {{#if isPending}} <p>Loading...</p> {{else}} {{#each item in promise._result }} <p>{{item.reason}}</p> {{/each}} {{/if}} 

在您的模板中,您可以根据承诺的状态呈现不同的内容。 当你的页面最初加载时,你的“我爱干酪的原因”部分将显示Loading... 当承诺被加载时,它将呈现与模型的每个logging相关联的所有原因。

每个部分将独立加载,不会立即阻止主要内容的渲染。

这是一个简单的例子,但我希望其他人都认为它和我一样有用。

如果你想要为许多内容行做类似的事情,你可能会发现上面的Novelys例子更加相关。 如果没有,上述应该适合你。

这可能不是最好的做法和天真的做法,但它概念地显示了如何在一条中心路线上提供多种模型:

 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); } }); 

希望它有帮助

感谢所有其他优秀的答案,我创build了一个混合,将最好的解决scheme结合到一个简单且可重用的界面中。 它在afterModel为你指定的模型执行一个Ember.RSVP.hash ,然后把这些属性注入到setupController的控制器中。 它不会干扰标准的model钩子,所以你仍然可以将其定义为正常。

使用示例:

 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') } } }); 

这里是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适用于相关模型。; 但是,对于Ember CLI和Ember Data的最新版本,对于不相关的模型有一个更简单的方法:

 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) }); } }); 

如果您只想检索model2的对象的属性,请使用DS.PromiseObject而不是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')) }); } }); 

添加到MilkyWayJoe的答案,谢谢btw:

 this.store.find('post',1) 

回报

 { id: 1, title: 'string', body: 'string string string string...', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] }; 

作者会

 { id: 1, firstName: 'Joe', lastName: 'Way', email: 'MilkyWayJoe@example.com', points: 6181, post_ids: [1,2,3,...,n], comment_ids: [1,2,3,...,n], } 

注释

 { id:1, author_id:1, body:'some words and stuff...', post_id:1, } 

…我相信链接是重要的,以便build立完整的关系。 希望能帮助别人。

您可以使用beforeModelafterModel钩子,因为这些钩子总是被调用的,即使model没有被调用,因为您正在使用dynamic段。

根据asynchronous路由文档:

模型钩子涵盖了许多用于暂停约定转换的用例,但有时您需要beforeModel和afterModel的相关钩子的帮助。 最常见的原因是,如果您通过{{link-to}}或transitionTo(而不是由URL更改导致的转换)转换为具有dynamicurl段的路线,则路线模型已经被指定(例如{{#链接到'article'文章}}或this.transitionTo('article',article)),在这种情况下,模型钩子不会被调用。 在这些情况下,您需要使用beforeModel或者afterModel钩子来保存任何逻辑,而路由器仍然收集所有的路由模型来执行转换。

所以说你的SiteController有一个themes属性,你可以有这样的东西:

 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')); }