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

时间:2013-05-09 14:16:52

标签: ember.js ember-data

通过阅读文档,您似乎必须(或应该)将模型分配给这样的路线:

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

如果我需要在某个路线中使用多个物体怎么办?即帖子,评论和用户?如何告知路由加载?

8 个答案:

答案 0 :(得分:116)

上次永久更新:我无法继续更新此内容。所以这已被弃用,可能会这样。这是一个更好,更新的线程EmberJS: How to load multiple models on the same route?

更新:在我原来的回答中,我说过在模型定义中使用embedded: true。那是不对的。在修订版12中,Ember-Data期望使用后缀(link_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之间的associations,并仅在该路线中加载Post模型,考虑到我可以在DS.hasManyComment模型中为DS.belongsTo模型和User的{​​{1}}关联定义Comment关联。像这样:

Post

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

Associations between models

有了这个定义,每当我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') }); 发帖时,我都可以访问与该帖子相关的评论集合,以及评论的作者,以及作为帖子作者的用户< s>因为它们都是嵌入的。路线保持简单:

find

所以在App.PostsPostRoute = Em.Route.extend({ model: function(params) { return App.Post.find(params.post_id); } }); (或PostRoute,如果您使用的是PostsPostRoute),我的模板将可以访问控制器的resource,即{{1}我可以引用作者,简单地称为content

Post

(见fiddle


回答非相关模型:

但是,如果您的方案比您描述的方案稍微复杂,并且/或 使用(或查询)特定路线的不同模型,我建议您在{ {3}}。例如:

author

现在,当我处于Post路径时,我的模板将可以访问控制器中的<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> 属性,因为它是在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)); } }); hook中设置的:

user

(见Route#setupController

答案 1 :(得分:49)

使用Em.Object封装多个模型是获取model挂钩中所有数据的好方法。但它无法确保在视图渲染后准备好所有数据。

另一种选择是使用Em.RSVP.hash。它将多个承诺结合在一起并返回新的承诺。在所有承诺得到解决后解决的新承诺。在承诺得到解决或拒绝之前,不会调用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.RSVPhttps://github.com/tildeio/rsvp.js


但如果您的路线有动态细分,请不要使用Em.ObjectEm.RSVP解决方案

主要问题是link-to。如果您通过点击link-to生成的链接更改网址,则会将模型直接传递到该路线。 在这种情况下,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代码,帖子是模型:

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

这里的事情变得有趣。当您使用URL /post/1访问该页面时,将调用model挂钩,并且当承诺解析时,setupController将获取普通对象。

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

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

答案 2 :(得分:13)

有一段时间我使用的是Em.RSVP.hash,但我遇到的问题是我不希望我的视图等到所有模型都在渲染之前加载。但是,我发现了一个很好的(但相对未知的)解决方案,感谢Novelys的人们使用了Ember.PromiseProxyMixin

让我们假设你有一个有三个不同视觉部分的视图。这些部分中的每一部分都应该由自己的模型支持。支持&#34; splash&#34;视图顶部的内容很小,并且会快速加载,因此您可以正常加载该内容:

创建路线main-page.js

import Ember from 'ember';

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

然后您可以创建相应的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>

所以,让我们在你的模板中说你想要有关于你与奶酪的爱/恨关系的单独部分,每个(由于某种原因)由自己的模型支持。您在每个模型中都有许多记录,其中包含与每个原因相关的详细信息,但是您希望快速渲染顶部的内容。这是{{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>

现在,您需要为每个人创建控制器和模板。由于他们在这个例子中实际上是相同的,所以我只使用一个。

创建一个名为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,这使得控制器可以发出承诺。初始化控制器时,我们指出promise应该通过Ember Data加载love-cheese模型。您需要在控制器的promise属性上设置此属性。

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

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

在您的模板中,您可以根据承诺状态呈现不同的内容。当您的页面最初加载时,您的&#34;原因我喜欢奶酪&#34;部分将显示Loading...。加载承诺后,它将呈现与模型的每个记录关联的所有原因。

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

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

如果您正在寻找针对多行内容执行类似操作的内容,您可能会发现上面的Novelys示例更具相关性。如果没有,上述情况应该适合你。

答案 3 :(得分:8)

这可能不是最佳做法和天真的做法,但从概念上说明了如何在一条中央路线上使用多种型号:

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

希望有所帮助

答案 4 :(得分:4)

感谢所有其他优秀的答案,我创建了一个mixin,将最佳解决方案结合到一个简单且可重复使用的界面中。它为您指定的模型在Ember.RSVP.hash中执行afterModel,然后将属性注入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);
  }
});

答案 5 :(得分:2)

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

答案 6 :(得分:1)

加入MilkyWayJoe的回答,谢谢顺便说一句:

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

... 我认为链接支持很重要,因此建立了完整的关系。希望有所帮助。

答案 7 :(得分:0)

您可以使用beforeModelafterModel挂钩,因为这些挂钩总是被调用,即使由于您使用的是动态细分,因此未调用model

根据asynchronous routing文档:

  

模型钩子涵盖了许多用于暂停转换的用例,但有时你需要在模型和afterModel之前的相关钩子的帮助。最常见的原因是,如果您通过{{link-to}}或transitionTo过渡到具有动态URL段的路线(而不是由URL更改引起的转换),那么路线的模型已经指定了转换为will(例如{{#link-to'article'article}}或this.transitionTo('article',article)),在这种情况下,不会调用模型钩子。在这些情况下,您需要使用beforeModel或afterModel挂钩来容纳任何逻辑,而路由器仍在收集所有路由的模型以执行转换。

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

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