Как использовать несколько моделей с одним маршрутом в EmberJS/Ember Data?
Из чтения документов, похоже, вам нужно (или должно) назначить модель для такого маршрута:
App.PostRoute = Ember.Route.extend({
model: function() {
return App.Post.find();
}
});
Что делать, если мне нужно использовать несколько объектов на определенном маршруте? т.е. сообщения, комментарии и пользователи? Как указать маршрут для загрузки?
Ответы
Ответ 1
Последнее обновление навсегда. Я не могу продолжать обновлять это. Поэтому это устарело и, вероятно, будет таким. здесь лучше, и более свежий поток 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: true
) и загружать только модель Post
в этом маршруте, учитывая, что я могу определить ассоциацию DS.hasMany
для модели Comment
и DS.belongsTo
для User
в моделях 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')
});
Это определение приведет к следующему:
![Associations between models]()
С этим определением всякий раз, когда я find
Post, у меня будет доступ к коллекции комментариев, связанных с этим сообщением, и к автору комментария, а также к пользователю, который является автором сообщения, так как все они встроены. Маршрут остается простым:
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>
(см. fiddle)
Ответ на не связанные модели:
Однако, если ваш сценарий немного сложнее описанного вами и/или должен использовать (или запрашивать) разные модели для определенного маршрута, я бы рекомендовал сделать это в 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 template
controller.set('user', App.User.find(window.user_id));
}
});
И теперь, когда я нахожусь на пути "Почта", мои шаблоны будут иметь доступ к свойству User
в контроллере, поскольку он был настроен в setupController
hook:
<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>
(см. fiddle)
Ответ 2
Использование Em.Object
для инкапсуляции нескольких моделей - хороший способ получить все данные в model
hook. Но он не может гарантировать, что все данные будут подготовлены после рендеринга представления.
Другой выбор - использовать Em.RSVP.hash
. Он объединяет несколько promises вместе и возвращает новое обещание. Новое обещание, если разрешено после всех promises, будет разрешено. И 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.Object
или Em.RSVP
решение, если ваш маршрут имеет динамические сегменты
Основная проблема - link-to
. Если вы измените URL-адрес по ссылке, сгенерированной с помощью 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
hook, а setupController
получает простой объект, когда обещание разрешено.
Но если вы заходите на страницу по ссылке link-to
, она передает модель post
на PostRoute
, и маршрут игнорирует крюк model
. В этом случае setupController
получит модель post
, конечно, вы не сможете получить пользователя.
Поэтому убедитесь, что вы не используете их в маршрутах с динамическими сегментами.
Ответ 3
Некоторое время я использовал Em.RSVP.hash
, однако проблема, с которой я столкнулся, заключалась в том, что я не хотел, чтобы мое представление дождалось загрузки всех моделей до рендеринга. Тем не менее, я нашел отличное (но относительно неизвестное) решение благодаря людям в Novelys, что предполагает использование Ember.PromiseProxyMixin:
Скажем, у вас есть представление, которое имеет три разных визуальных раздела. Каждый из этих разделов должен подкрепляться собственной моделью. Модель, поддерживающая содержимое "всплеска" в верхней части представления, мала и будет загружаться быстро, поэтому вы можете загрузить ее обычно:
Создайте маршрут 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...
. Когда обещание будет загружено, оно отобразит все причины, связанные с каждой записью вашей модели.
Каждая секция будет загружаться независимо и не будет немедленно блокировать основной контент от рендеринга.
Это упрощенный пример, но я надеюсь, что все остальные считают его полезным, как и я.
Если вы хотите сделать что-то подобное для многих строк контента, вы можете найти пример Novelys выше даже более актуальным. Если нет, вышеприведенное должно работать нормально для вас.
Ответ 4
Это может быть не самая лучшая практика и наивный подход, но она концептуально показывает, как вы могли бы использовать несколько моделей на одном центральном маршруте:
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);
}
});
надеюсь, что это поможет
Ответ 5
Благодаря всем остальным отличным ответам, я создал mixin, который объединяет лучшие решения здесь в простой и многоразовый интерфейс. Он выполняет Ember.RSVP.hash
в afterModel
для указанных вами моделей, затем вводит свойства в контроллер в setupController
. Он не мешает стандарту model
hook, поэтому вы все равно определяете это как обычно.
Пример использования:
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);
}
});
Ответ 6
fooobar.com/questions/67030/... отлично подходит для связанных моделей. Однако с недавней версией 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'))
});
}
});
Ответ 7
Добавляя к 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: '[email protected]',
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,
}
...
Я считаю, что обратные ссылки важны, чтобы установить полную связь. Надежда, которая помогает кому-то.
Ответ 8
Вы можете использовать крючки beforeModel
или afterModel
, поскольку они всегда вызываются, даже если model
не вызывается, потому что вы используете динамические сегменты.
В соответствии с асинхронная маршрутизация docs:
Крючок модели охватывает множество вариантов использования для переходов с паузой на обещание, но иногда вам понадобится помощь связанных крючков beforeModel и afterModel. Наиболее распространенная причина этого заключается в том, что если вы переходите на маршрут с динамическим сегментом URL через {{link-to}} или transitionTo (в отличие от перехода, вызванного изменением 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'));
}