86

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

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

如果我需要在某个路由中使用多个对象怎么办?即帖子、评论和用户?我如何告诉加载这些的路线?

4

8 回答 8

117

永远最后一次更新:我不能继续更新这个。所以这已被弃用,很可能会是这样。这是一个更好,更新的线程EmberJS:如何在同一条路线上加载多个模型?

更新:在我原来的答案中,我说要embedded: true在模型定义中使用。这是不正确的。在修订版 12 中,Ember-Data 期望为单个记录或集合定义带有后缀(链接)的外键。类似于以下内容:_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: truePost并仅在该路径中加载模型,考虑到我可以定义模型的DS.hasMany关联和模型的关联。像这样的东西:CommentDS.belongsToUserCommentPost

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发布帖子时,我都可以访问与该帖子相关的评论集合,以及评论的作者,以及作为帖子作者的用户,因为它们都是嵌入的。路线很简单:

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>

(见小提琴


用不相关的模型回答:

但是,如果您的场景比您描述的要复杂一些,和/或必须为特定路线使用(或查询)不同的模型,我建议您在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));
    }
});

现在,当我在 Post 路由中时,我的模板将可以访问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>

(见小提琴

于 2013-05-09T16:19:18.463 回答
49

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

另一种选择是使用Em.RSVP.hash. 它将几个 Promise 组合在一起并返回一个新的 Promise。新的 Promise 如果在所有 Promise 都解决后解决。并且setupController在 promise 被解决或拒绝之前不会被调用。

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.Object或解决方案Em.RSVP

主要问题是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代码,帖子是一个模型:

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

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

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

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

于 2013-10-19T14:10:08.180 回答
13

有一段时间我在使用Em.RSVP.hash,但我遇到的问题是我不希望我的视图等到所有模型都加载完毕后再渲染。但是,感谢Novelys的人员,我找到了一个很棒的(但相对未知的)解决方案,其中涉及使用Ember.PromiseProxyMixin

假设您有一个具有三个不同视觉部分的视图。这些部分中的每一个都应由其自己的模型支持。在视图顶部支持“splash”内容的模型很小,并且会快速加载,因此您可以正常加载该模型:

创建路线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);
        }
    }
});

您会注意到我们正在使用PromiseProxyMixinhere,这使控制器具有承诺意识。当控制器初始化时,我们指出承诺应该love-cheese通过 Ember Data 加载模型。您需要在控制器的属性上设置此promise属性。

现在,创建一个名为love-cheese.hbs

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

在您的模板中,您将能够根据承诺状态呈现不同的内容。当您的页面最初加载时,您的“我喜欢奶酪的原因”部分将显示Loading...。当 Promise 被加载时,它将呈现与模型的每条记录相关的所有原因。

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

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

如果您希望对多行内容执行类似的操作,您可能会发现上面的 Novellys 示例更加相关。如果没有,上述内容应该适合您。

于 2015-01-29T16:19:04.497 回答
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);
  }
});

希望能帮助到你

于 2013-05-09T15:19:29.663 回答
4

感谢所有其他出色的答案,我创建了一个 mixin,将这里的最佳解决方案组合成一个简单且可重用的界面。它为您指定的模型执行一个Ember.RSVP.hashin 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')
    }
  }
});

这是混合:

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);
  }
});
于 2014-10-18T01:35:05.003 回答
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'))
    });
  }
});
于 2015-05-05T13:02:18.600 回答
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,
}

...我相信链接很重要,这样才能建立完整的关系。希望对某人有所帮助。

于 2014-07-22T02:44:59.050 回答
0

您可以使用beforeModelorafterModel钩子,因为它们总是被调用,即使model因为您使用动态段而没有被调用。

根据异步路由文档:

模型钩子涵盖了暂停承诺转换的许多用例,但有时您需要相关钩子 beforeModel 和 afterModel 的帮助。最常见的原因是,如果您通过 {{link-to}} 或 transitionTo 转换到具有动态 URL 段的路由(而不是由 URL 更改引起的转换),您的路由模型'正在过渡到将已经指定(例如 {{#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'));
}
于 2014-02-22T06:19:14.550 回答