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


85

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

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

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

Answers:


117

永远最后一次更新:我无法继续更新。因此,不建议使用此方法,并且可能采用这种方式。这是一个更好,更新的线程EmberJS:如何在同一条路线上加载多个模型?

更新:在我最初的回答中,我说过要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,并只加载Post模型,这条路线,考虑到我可以定义一个DS.hasMany用于关联Comment模型,并DS.belongsTo为协会User在这两个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发布一个帖子时,我都可以访问与该帖子相关的评论集合,以及评论的作者以及该帖子作者的用户,因为它们都是嵌入式的。路线保持简单:

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

现在,当我处于“发布”路线中时,我的模板将可以访问usersetupController钩子中设置的控制器中的属性:

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

(参见小提琴


15
谢谢你这么多采取发布的时候,我发现它真的很有用。
匿名

1
@MilkyWayJoe,非常好的帖子!现在我的方法看起来很幼稚:)
直观的

与您无关的模型的问题在于,它不像模型挂钩那样接受承诺,对吗?有什么解决方法吗?
miguelcobain 2014年

1
如果我正确理解了您的问题,则可以轻松地进行调整。只需等待诺言兑现,然后将模型设置为控制器的变量即可
Fabio 2014年

除了迭代和显示评论之外,如果您可以举一个例子说明有人可以如何向中添加新评论,那将是很不错的post.comments
2014年

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.RSVP以下内容的更多信息:https : //github.com/tildeio/rsvp.js


但是,如果您的路线具有动态路段,请不要使用Em.ObjectEm.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模型,当然不能获得用户。

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


2
我的答案适用于早期版本的Ember和Ember-Data。这是一个非常好的方法+1
MilkyWayJoe

11
其实有。如果您要将模型ID而不是模型本身传递给链接到帮助程序,则总是会触发模型挂钩。
darkbaby123

2
这应该在Ember指南的某处记录(最佳做法或类似内容)。我敢肯定,这是一个重要的用例,很多人都会遇到。
yorbro 2014年

1
如果正在使用,controller.setProperties(model);请不要忘记将具有默认值的这些属性添加到控制器。否则会引发异常Cannot delegate set...
Mateusz Nowak

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

您会注意到,我们在PromiseProxyMixin这里使用,这使控制器能够感知承诺。初始化控制器后,我们指示应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后,它将呈现与模型的每个记录相关的所有原因。

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

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

如果您希望对许多行内容执行类似的操作,则可能会发现上面的Novelys示例更加相关。如果不是这样,上面的方法对您来说很好。


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

希望能帮助到你


1
这种方法很好,只是没有过多利用Ember Data。对于某些模型不相关的情况,我会寻求与此类似的东西。
MilkyWayJoe

4

感谢所有其他出色的答案,我创建了一个mixin,将此处的最佳解决方案组合到一个简单且可重用的界面中。它执行的Ember.RSVP.hashafterModel您指定的型号,然后注入的属性到控制器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);
  }
});

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

我有一个post编辑路线/视图,我想在其中渲染数据库中的所有现有标签,以便用户可以单击以将其添加到正在编辑的帖子中。我想定义一个代表这些标签的数组/集合的变量。您上面使用的方法是否可以做到这一点?
2015年

当然,您将创建一个PromiseArray(例如“标签”)。然后,在模板中,将其传递到相应表单的选择元素。
2015年

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

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


0

您可以使用beforeModelafterModel挂钩,因为它们始终被调用,即使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'));
}

1
我认为this在Promise中使用将产生错误消息。您可以var _this = this在返回之前进行设置,然后_this.set(then(方法内进行操作以获得所需的结果
Duncan Walker
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.