Последнее обновление навсегда : я не могу продолжать обновлять это. Так что это устарело и, скорее всего, будет таким. вот лучшая и более современная ветка 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')
});
Это определение даст примерно следующее:
Благодаря этому определению, всякий раз, когда я 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);
},
setupController: function(controller, model) {
controller.set('content', model);
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>
(см. скрипку )
post.comments
.Использование
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.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
вызывается ловушка, котораяsetupController
получает простой объект после выполнения обещания.Но если вы посещаете страницу по щелчку
link-to
ссылки, она передаетpost
модель,PostRoute
а маршрут игнорируетmodel
ловушку. В этом случаеsetupController
вы получитеpost
модель, конечно, вы не сможете получить пользователя.Поэтому убедитесь, что вы не используете их в маршрутах с динамическими сегментами.
источник
controller.setProperties(model);
, не забудьте добавить эти свойства со значением по умолчанию в контроллер. В противном случае будет выдано исключениеCannot delegate set...
Некоторое время я использовал
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 даже более подходящим. Если нет, то вышеуказанное должно сработать для вас.
источник
Возможно, это не лучшая практика и наивный подход, но он концептуально показывает, как можно было бы использовать несколько моделей на одном центральном маршруте:
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); } });
Надеюсь, это поможет
источник
Благодаря всем остальным отличным ответам я создал миксин, который объединяет лучшие решения здесь в простой и многоразовый интерфейс. Он выполняет
Ember.RSVP.hash
inafterModel
для указанных вами моделей, а затем вводит свойства в контроллер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); } });
источник
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
маршрут / представление редактирования, где я хочу отобразить все существующие теги в БД, чтобы пользователь мог щелкнуть, чтобы добавить их в редактируемую запись. Я хочу определить переменную, представляющую массив / коллекцию этих тегов. Подойдет ли для этого подход, который вы использовали выше?PromiseArray
(например, «теги»). Затем в своем шаблоне вы передадите его элементу выбора соответствующей формы.Добавление к ответу 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, }
... Я считаю, что обратные ссылки важны для установления полных отношений. Надеюсь, это кому-то поможет.
источник
Вы можете использовать хуки
beforeModel
или,afterModel
поскольку они всегда вызываются, даже еслиmodel
не вызываются, потому что вы используете динамические сегменты.Согласно документам асинхронной маршрутизации :
Скажем, у вас есть
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')); }
источник
this
внутри обещания даст сообщение об ошибке. Вы можете установитьvar _this = this
перед возвратом, а затем выполнить_this.set(
внутриthen(
метода, чтобы получить желаемый результат