如何在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
关联定义Comment
和Post
模型中的用户。 像这样的东西:
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.Object
或Em.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立完整的关系。 希望能帮助别人。
您可以使用beforeModel
或afterModel
钩子,因为这些钩子总是被调用的,即使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')); }