Última atualização para sempre : não posso continuar atualizando isso. Portanto, ele está obsoleto e provavelmente será assim. aqui está um tópico melhor e mais atualizado EmberJS: Como carregar vários modelos na mesma rota?
Update: Na minha resposta original eu disse para usar embedded: true
na definição do modelo. Isso está incorreto. Na revisão 12, Ember-Data espera que as chaves estrangeiras sejam definidas com um sufixo ( link ) _id
para registro único ou _ids
para coleta. Algo semelhante ao seguinte:
{
id: 1,
title: 'string',
body: 'string string string string...',
author_id: 1,
comment_ids: [1, 2, 3, 6],
tag_ids: [3,4]
}
Eu atualizei o violino e o farei novamente se algo mudar ou se eu encontrar mais problemas com o código fornecido nesta resposta.
Responda com modelos relacionados:
Para o cenário que você está descrevendo, eu confiaria em associações entre modelos (configuração embedded: true
) e apenas carregaria o Post
modelo nessa rota, considerando que posso definir uma DS.hasMany
associação para o Comment
modelo e uma DS.belongsTo
associação para User
nos modelos Comment
e Post
. Algo assim:
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')
});
Essa definição produziria algo como o seguinte:
Com essa definição, sempre que eu find
postar, terei acesso a uma coleção de comentários associados a essa postagem, bem como ao autor do comentário e ao usuário que é o autor da postagem, uma vez que estão todos incorporados . A rota permanece simples:
App.PostsPostRoute = Em.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
}
});
Portanto, no PostRoute
(ou PostsPostRoute
se você estiver usando resource
), meus modelos terão acesso ao controlador content
, que é o Post
modelo, então posso me referir ao autor, simplesmente comoauthor
<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>
(ver violino )
Responder com modelos não relacionados:
No entanto, se o seu cenário for um pouco mais complexo do que o que você descreveu e / ou tiver que usar (ou consultar) modelos diferentes para uma rota específica, recomendo fazê-lo em Route#setupController
. Por exemplo:
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));
}
});
E agora, quando estou na rota Post, meus modelos terão acesso à user
propriedade no controlador conforme foi configurado no setupController
gancho:
<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>
(ver violino )
post.comments
.Usar
Em.Object
para encapsular vários modelos é uma boa maneira de colocar todos os dados nomodel
gancho. Mas não pode garantir que todos os dados sejam preparados após a renderização da visualização.Outra opção é usar
Em.RSVP.hash
. Ele combina várias promessas e retorna uma nova promessa. A nova promessa é resolvida depois que todas as promessas são resolvidas. EsetupController
não é chamado até que a promessa seja resolvida ou rejeitada.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); } });
Desta forma, você obtém todos os modelos antes da renderização da vista. E usar
Em.Object
não tem essa vantagem.Outra vantagem é que você pode combinar promessa e não promessa. Como isso:
Em.RSVP.hash({ post: // non-promise object user: // promise object });
Verifique aqui para saber mais sobre
Em.RSVP
: https://github.com/tildeio/rsvp.jsMas não use
Em.Object
ouEm.RSVP
solução se sua rota tem segmentos dinâmicosO principal problema é
link-to
. Se você alterar o url clicando no link gerado porlink-to
com modelos, o modelo é passado diretamente para aquela rota. Neste caso, omodel
gancho não é chamado esetupController
você pega o modelolink-to
que lhe dá.Um exemplo é assim:
O código da rota:
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); } });
E o
link-to
código, o post é um modelo:{{#link-to "post" post}}Some post{{/link-to}}
As coisas ficam interessantes aqui. Quando você usa url
/post/1
para visitar a página, omodel
gancho é chamado esetupController
obtém o objeto simples quando a promessa é resolvida.Mas se você visitar a página clicando no
link-to
link, ele passa opost
modelo paraPostRoute
e a rota irá ignorar omodel
gancho. Neste casosetupController
, obterá opost
modelo, é claro que você não pode obter o usuário.Portanto, certifique-se de não usá-los em rotas com segmentos dinâmicos.
fonte
controller.setProperties(model);
não se esqueça de adicionar essas propriedades com valor padrão ao controlador. Caso contrário, lançará exceçãoCannot delegate set...
Por um tempo eu estava usando
Em.RSVP.hash
, mas o problema que encontrei foi que eu não queria que minha visualização esperasse até que todos os modelos estivessem carregados antes de renderizar. No entanto, encontrei uma ótima solução (mas relativamente desconhecida) graças ao pessoal da Novelys que envolve o uso do Ember.PromiseProxyMixin :Digamos que você tenha uma visualização com três seções visuais distintas. Cada uma dessas seções deve ser apoiada por seu próprio modelo. O modelo que apoia o conteúdo "inicial" na parte superior da visualização é pequeno e carregará rapidamente, então você pode carregá-lo normalmente:
Crie uma rota
main-page.js
:import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.store.find('main-stuff'); } });
Em seguida, você pode criar um modelo de guiador correspondente
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>
Então, digamos que em seu modelo você queira ter seções separadas sobre sua relação de amor / ódio com o queijo, cada uma (por algum motivo) apoiada por seu próprio modelo. Você tem muitos registros em cada modelo com muitos detalhes relacionados a cada motivo, mas gostaria que o conteúdo acima fosse renderizado rapidamente. É aqui que
{{render}}
entra o auxiliar. Você pode atualizar seu modelo da seguinte maneira:<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>
Agora você precisará criar controladores e modelos para cada um. Já que eles são efetivamente idênticos para este exemplo, usarei apenas um.
Crie um controlador chamado
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); } } });
Você notará que estamos usando o
PromiseProxyMixin
here, o que torna o controlador ciente da promessa. Quando o controlador é inicializado, indicamos que a promessa deve ser carregar olove-cheese
modelo via Ember Data. Você precisará definir esta propriedade na propriedade do controladorpromise
.Agora, crie um modelo chamado
love-cheese.hbs
:{{#if isPending}} <p>Loading...</p> {{else}} {{#each item in promise._result }} <p>{{item.reason}}</p> {{/each}} {{/if}}
Em seu modelo, você será capaz de renderizar conteúdo diferente dependendo do estado de promessa. Quando sua página for carregada inicialmente, a seção "Razões para adorar queijo" será exibida
Loading...
. Quando a promessa for carregada, ela renderizará todos os motivos associados a cada registro de seu modelo.Cada seção carregará independentemente e não bloqueará a renderização do conteúdo principal imediatamente.
Este é um exemplo simplista, mas espero que todos o considerem tão útil quanto eu.
Se você está procurando fazer algo semelhante para muitas linhas de conteúdo, pode achar o exemplo da Novelys acima ainda mais relevante. Caso contrário, o procedimento acima deve funcionar bem para você.
fonte
Isso pode não ser uma prática recomendada e uma abordagem ingênua, mas mostra conceitualmente como você faria para ter vários modelos disponíveis em uma rota central:
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); } });
espero que ajude
fonte
Graças a todas as outras excelentes respostas, criei um mixin que combina as melhores soluções aqui em uma interface simples e reutilizável. Ele executa um
Ember.RSVP.hash
inafterModel
para os modelos que você especifica e, em seguida, injeta as propriedades no controlador emsetupController
. Ele não interfere com omodel
gancho padrão , então você ainda definiria isso como normal.Exemplo de uso:
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') } } });
Aqui está o 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); } });
fonte
https://stackoverflow.com/a/16466427/2637573 é adequado para modelos relacionados. No entanto, com a versão recente do Ember CLI e Ember Data, há uma abordagem mais simples para modelos não relacionados:
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) }); } });
Se você deseja recuperar apenas a propriedade de um objeto para
model2
, use DS.PromiseObject em vez de 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')) }); } });
fonte
post
rota / visualização de edição onde desejo renderizar todas as tags existentes no banco de dados para que o usuário possa clicar para adicioná-las ao post que está sendo editado. Quero definir uma variável que representa uma matriz / coleção dessas marcas. A abordagem que você usou acima funcionaria para isso?PromiseArray
(por exemplo, "tags"). Então, em seu modelo, você o passaria para o elemento de seleção do formulário correspondente.Acrescentando à resposta do MilkyWayJoe, obrigado a propósito:
this.store.find('post',1)
retorna
{ id: 1, title: 'string', body: 'string string string string...', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] };
autor seria
{ id: 1, firstName: 'Joe', lastName: 'Way', email: '[email protected]', points: 6181, post_ids: [1,2,3,...,n], comment_ids: [1,2,3,...,n], }
comentários
{ id:1, author_id:1, body:'some words and stuff...', post_id:1, }
... acredito que os links backs são importantes para que o relacionamento pleno seja estabelecido. Espero que ajude alguém.
fonte
Você poderia usar os ganchos
beforeModel
ou,afterModel
pois eles são sempre chamados, mesmo semodel
não forem chamados porque você está usando segmentos dinâmicos.De acordo com os documentos de roteamento assíncrono :
Então, digamos que você tenha uma
themes
propriedade em seuSiteController
, você poderia ter algo assim: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')); }
fonte
this
dentro da promessa geraria uma mensagem de erro. Você pode definirvar _this = this
antes do retorno e, em seguida, fazer_this.set(
dentro dothen(
método para obter o resultado desejado