Dernière mise à jour pour toujours : je ne peux pas continuer à mettre à jour ceci. C'est donc obsolète et ce sera probablement le cas. voici un fil de discussion meilleur et plus à jour EmberJS: Comment charger plusieurs modèles sur le même itinéraire?
Mise à jour: Dans ma réponse initiale, j'ai dit d'utiliser embedded: true
dans la définition du modèle. C'est incorrect. Dans la révision 12, Ember-Data s'attend à ce que les clés étrangères soient définies avec un suffixe ( lien ) _id
pour un seul enregistrement ou _ids
pour la collecte. Quelque chose de similaire à ce qui suit:
{
id: 1,
title: 'string',
body: 'string string string string...',
author_id: 1,
comment_ids: [1, 2, 3, 6],
tag_ids: [3,4]
}
J'ai mis à jour le violon et je le ferai à nouveau si quelque chose change ou si je trouve plus de problèmes avec le code fourni dans cette réponse.
Réponse avec des modèles associés:
Pour le scénario que vous décrivez, je compter sur les associations entre les modèles (réglage embedded: true
) et seulement charger le Post
modèle dans cette voie, étant donné que je peux définir une DS.hasMany
association pour le Comment
modèle et l' DS.belongsTo
association pour le User
dans les deux Comment
et Post
modèles. Quelque chose comme ça:
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')
});
Cette définition produirait quelque chose comme ce qui suit:
Avec cette définition, chaque fois que find
je publierai un message, j'aurai accès à une collection de commentaires associés à ce message, ainsi qu'à l'auteur du commentaire et à l'utilisateur qui est l'auteur du message, car ils sont tous intégrés . L'itinéraire reste simple:
App.PostsPostRoute = Em.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
}
});
Donc, dans le PostRoute
(ou PostsPostRoute
si vous utilisez resource
), mes modèles auront accès au contrôleur content
, qui est le Post
modèle, afin que je puisse me référer à l'auteur, simplement commeauthor
<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>
(voir violon )
Réponse avec des modèles non liés:
Cependant, si votre scénario est un peu plus complexe que ce que vous avez décrit et / ou si vous devez utiliser (ou interroger) différents modèles pour un itinéraire particulier, je vous recommande de le faire dans Route#setupController
. Par exemple:
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));
}
});
Et maintenant, lorsque je suis dans la route Post, mes modèles auront accès à la user
propriété dans le contrôleur tel qu'il a été configuré dans 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>
(voir violon )
post.comments
.Utiliser
Em.Object
pour encapsuler plusieurs modèles est un bon moyen d'obtenir toutes les données enmodel
hook. Mais il ne peut pas garantir que toutes les données sont préparées après le rendu de la vue.Un autre choix consiste à utiliser
Em.RSVP.hash
. Il combine plusieurs promesses ensemble et retourne une nouvelle promesse. La nouvelle promesse est résolue une fois que toutes les promesses sont résolues. EtsetupController
n'est pas appelée tant que la promesse n'est pas résolue ou rejetée.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); } });
De cette façon, vous obtenez tous les modèles avant le rendu de la vue. Et l'utilisation
Em.Object
n'a pas cet avantage.Un autre avantage est que vous pouvez combiner promesse et non-promesse. Comme ça:
Em.RSVP.hash({ post: // non-promise object user: // promise object });
Cochez ceci pour en savoir plus
Em.RSVP
: https://github.com/tildeio/rsvp.jsMais ne pas utiliser
Em.Object
ouEm.RSVP
solution si votre itinéraire comporte des segments dynamiquesLe problème principal est
link-to
. Si vous modifiez l'URL en cliquant sur le lien généré parlink-to
with models, le modèle est transmis directement à cette route. Dans ce cas, lemodel
crochet n'est pas appelé etsetupController
vous obtenez le modèle quilink-to
vous est donné.Un exemple est comme ceci:
Le code d'itinéraire:
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); } });
Et le
link-to
code, le post est un modèle:{{#link-to "post" post}}Some post{{/link-to}}
Les choses deviennent intéressantes ici. Lorsque vous utilisez url
/post/1
pour visiter la page, lemodel
hook est appelé etsetupController
obtient l'objet brut lorsque la promesse est résolue.Mais si vous visitez la page en cliquant sur le
link-to
lien, il passe lepost
modèle àPostRoute
et l'itinéraire ignorera lemodel
hook. Dans ce cassetupController
, vous obtiendrez lepost
modèle, bien sûr, vous ne pouvez pas obtenir d'utilisateur.Assurez-vous donc de ne pas les utiliser dans les itinéraires avec des segments dynamiques.
la source
controller.setProperties(model);
n'oubliez pas d'ajouter ces propriétés avec la valeur par défaut au contrôleur. Sinon, il lancera une exceptionCannot delegate set...
Pendant un certain temps, j'utilisais
Em.RSVP.hash
, mais le problème que j'ai rencontré était que je ne voulais pas que ma vue attende que tous les modèles soient chargés avant le rendu. Cependant, j'ai trouvé une excellente solution (mais relativement inconnue) grâce aux gens de Novelys, qui consiste à utiliser Ember.PromiseProxyMixin :Supposons que vous ayez une vue comportant trois sections visuelles distinctes. Chacune de ces sections doit être soutenue par son propre modèle. Le modèle sauvegardant le contenu "splash" en haut de la vue est petit et se chargera rapidement, vous pouvez donc charger celui-là normalement:
Créez un itinéraire
main-page.js
:import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.store.find('main-stuff'); } });
Ensuite, vous pouvez créer un modèle de guidon correspondant
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>
Disons que dans votre modèle, vous souhaitez avoir des sections séparées sur votre relation d'amour / haine avec le fromage, chacune (pour une raison quelconque) soutenue par son propre modèle. Vous avez de nombreux enregistrements dans chaque modèle avec des détails détaillés relatifs à chaque raison, mais vous souhaitez que le contenu en haut soit rendu rapidement. C'est là que l'
{{render}}
assistant entre en jeu. Vous pouvez mettre à jour votre modèle comme suit:<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>
Vous devrez maintenant créer des contrôleurs et des modèles pour chacun. Puisqu'ils sont effectivement identiques pour cet exemple, je n'en utiliserai qu'un.
Créez un contrôleur appelé
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); } } });
Vous remarquerez que nous utilisons
PromiseProxyMixin
ici, ce qui rend le contrôleur conscient des promesses. Lorsque le contrôleur est initialisé, nous indiquons que la promesse devrait charger lelove-cheese
modèle via Ember Data. Vous devrez définir cette propriété sur la propriété du contrôleurpromise
.Maintenant, créez un modèle appelé
love-cheese.hbs
:{{#if isPending}} <p>Loading...</p> {{else}} {{#each item in promise._result }} <p>{{item.reason}}</p> {{/each}} {{/if}}
Dans votre modèle, vous pourrez rendre un contenu différent en fonction de l'état de la promesse. Lors du chargement initial de votre page, votre section «Raisons pour lesquelles j'aime le fromage» s'affichera
Loading...
. Lorsque la promesse est chargée, elle rendra toutes les raisons associées à chaque enregistrement de votre modèle.Chaque section se chargera indépendamment et n'empêchera pas le contenu principal de s'afficher immédiatement.
C'est un exemple simpliste, mais j'espère que tout le monde le trouvera aussi utile que moi.
Si vous cherchez à faire quelque chose de similaire pour de nombreuses lignes de contenu, vous pouvez trouver l'exemple Novelys ci-dessus encore plus pertinent. Sinon, ce qui précède devrait fonctionner correctement pour vous.
la source
Ce n'est peut-être pas la meilleure pratique et une approche naïve, mais cela montre conceptuellement comment vous feriez pour avoir plusieurs modèles disponibles sur un itinéraire 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); } });
J'espère que cela aide
la source
Grâce à toutes les autres excellentes réponses, j'ai créé un mixin qui combine les meilleures solutions ici dans une interface simple et réutilisable. Il exécute une
Ember.RSVP.hash
dansafterModel
les modèles que vous spécifiez, injectent ensuite les propriétés dans le contrôleursetupController
. Cela n'interfère pas avec lemodel
hook standard , donc vous définiriez toujours cela comme normal.Exemple d'utilisation:
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') } } });
Voici le 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); } });
la source
https://stackoverflow.com/a/16466427/2637573 convient aux modèles associés. Cependant, avec la version récente d'Ember CLI et d'Ember Data, il existe une approche plus simple pour les modèles indépendants:
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) }); } });
Si vous souhaitez uniquement récupérer la propriété d'un objet pour
model2
, utilisez DS.PromiseObject au lieu 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')) }); } });
la source
post
route / vue d'édition où je veux rendre toutes les balises existantes dans la base de données afin que l'utilisation puisse cliquer pour les ajouter au message en cours d'édition. Je souhaite définir une variable qui représente un tableau / une collection de ces balises. L'approche que vous avez utilisée ci-dessus fonctionnerait-elle pour cela?PromiseArray
(par exemple, "tags"). Ensuite, dans votre modèle, vous le passerez à l'élément de sélection du formulaire correspondant.Ajout à la réponse de MilkyWayJoe, merci btw:
this.store.find('post',1)
Retour
{ id: 1, title: 'string', body: 'string string string string...', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] };
l'auteur serait
{ id: 1, firstName: 'Joe', lastName: 'Way', email: '[email protected]', points: 6181, post_ids: [1,2,3,...,n], comment_ids: [1,2,3,...,n], }
commentaires
{ id:1, author_id:1, body:'some words and stuff...', post_id:1, }
... Je crois que les liens sont importants pour que la relation complète soit établie. J'espère que cela aide quelqu'un.
la source
Vous pouvez utiliser les hooks
beforeModel
ouafterModel
car ils sont toujours appelés, même s'ilsmodel
ne sont pas appelés car vous utilisez des segments dynamiques.Selon la documentation de routage asynchrone :
Alors disons que vous avez une
themes
propriété sur votreSiteController
, vous pourriez avoir quelque chose comme ceci: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')); }
la source
this
à l'intérieur de la promesse donnerait un message d'erreur. Vous pouvez définirvar _this = this
avant le retour, puis faire_this.set(
à l'intérieur de lathen(
méthode pour obtenir le résultat souhaité