Ostatnia aktualizacja na zawsze : nie mogę tego aktualizować. Więc to jest przestarzałe i prawdopodobnie tak będzie. oto lepszy i bardziej aktualny wątek EmberJS: Jak załadować wiele modeli na tej samej trasie?
Aktualizacja: W mojej pierwotnej odpowiedzi powiedziałem, aby użyć embedded: true
w definicji modelu. To jest niepoprawne. W wersji 12 Ember-Data oczekuje, że klucze obce będą definiowane z sufiksem ( łączem ) _id
dla pojedynczego rekordu lub _ids
do kolekcji. Coś podobnego do następującego:
{
id: 1,
title: 'string',
body: 'string string string string...',
author_id: 1,
comment_ids: [1, 2, 3, 6],
tag_ids: [3,4]
}
Zaktualizowałem skrzypce i zrobię to ponownie, jeśli coś się zmieni lub jeśli znajdę więcej problemów z kodem podanym w tej odpowiedzi.
Odpowiedz za pomocą powiązanych modeli:
W przypadku scenariusza, który opisujesz, oparłbym się na powiązaniach między modelami (ustawieniami embedded: true
) i załadowałbym Post
model tylko w tej trasie, biorąc pod uwagę, że mogę zdefiniować DS.hasMany
skojarzenie dla Comment
modelu i DS.belongsTo
skojarzenie dla User
obu modeli Comment
i Post
. Coś takiego:
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')
});
Ta definicja dałaby coś takiego:
Dzięki tej definicji za każdym razem, gdy piszę find
post, będę miał dostęp do zbioru komentarzy powiązanych z tym postem, a także autora komentarza oraz użytkownika, który jest autorem posta, ponieważ wszystkie są osadzone . Trasa pozostaje prosta:
App.PostsPostRoute = Em.Route.extend({
model: function(params) {
return App.Post.find(params.post_id);
}
});
Więc w PostRoute
(lub PostsPostRoute
jeśli używasz resource
) moje szablony będą miały dostęp do kontrolera content
, który jest Post
modelem, więc mogę odwołać się do autora, po prostu jakoauthor
<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>
(patrz skrzypce )
Odpowiedź z niepowiązanymi modelami:
Jeśli jednak Twój scenariusz jest nieco bardziej złożony niż ten, który opisałeś i / lub musisz używać (lub przeszukiwać) różne modele dla określonej trasy, polecam zrobić to w programie Route#setupController
. Na przykład:
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));
}
});
A teraz, gdy jestem w trasie postu, moje szablony będą miały dostęp do user
właściwości w kontrolerze, tak jak zostało ustawione w setupController
hooku:
<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>
(patrz skrzypce )
post.comments
.Używanie
Em.Object
do hermetyzacji wielu modeli to dobry sposób na przechwycenie wszystkich danychmodel
. Ale nie może zapewnić przygotowania wszystkich danych po wyrenderowaniu widoku.Innym wyborem jest użycie
Em.RSVP.hash
. Łączy kilka obietnic razem i zwraca nową obietnicę. Nowa obietnica, jeśli zostanie rozwiązana po rozwiązaniu wszystkich obietnic. IsetupController
nie jest powołany, dopóki obietnica nie zostanie rozwiązana lub odrzucona.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); } });
W ten sposób otrzymujesz wszystkie modele przed renderowaniem widoku. A używanie
Em.Object
nie ma tej przewagi.Kolejną zaletą jest to, że możesz łączyć obietnicę i brak obietnicy. Lubię to:
Em.RSVP.hash({ post: // non-promise object user: // promise object });
Sprawdź to, aby dowiedzieć się więcej o
Em.RSVP
: https://github.com/tildeio/rsvp.jsAle nie używaj
Em.Object
lubEm.RSVP
rozwiązania, jeśli Twoja trasa ma dynamiczne segmentyGłówny problem to
link-to
. Jeśli zmienisz adres URL, klikając link generowany przezlink-to
z modelami, model zostanie przekazany bezpośrednio do tej trasy. W tym przypadkumodel
hak nie jest wywoływany isetupController
dostajesz model,link-to
który ci daje.Przykład jest taki:
Kod trasy:
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); } });
A
link-to
kod, post to model:{{#link-to "post" post}}Some post{{/link-to}}
Tutaj robi się ciekawie. Kiedy używasz url
/post/1
do odwiedzenia strony,model
wywoływany jest haczyk isetupController
pobiera zwykły obiekt po rozwiązaniu obietnicy.Ale jeśli odwiedzasz stronę przez kliknięcie
link-to
linku, przekazujepost
model do,PostRoute
a trasa zignorujemodel
hak. W takim przypadkusetupController
dostanieszpost
model, oczywiście nie możesz pobrać użytkownika.Dlatego upewnij się, że nie używasz ich w trasach z dynamicznymi segmentami.
źródło
controller.setProperties(model);
, nie zapomnij dodać tych właściwości z wartością domyślną do kontrolera. Inaczej wyrzuci wyjątekCannot delegate set...
Przez jakiś czas używałem
Em.RSVP.hash
, jednak problem, na który natknąłem się, polegał na tym, że nie chciałem, aby mój widok czekał, aż wszystkie modele zostaną załadowane przed renderowaniem. Jednak dzięki ludziom z Novelys znalazłem świetne (ale stosunkowo nieznane) rozwiązanie, które polega na wykorzystaniu Ember. PromiseProxyMixin :Załóżmy, że masz widok, który ma trzy odrębne sekcje wizualne. Każda z tych sekcji powinna być poparta własnym modelem. Model, na którym umieszczona jest zawartość „splash” u góry widoku, jest mały i szybko się wczytuje, więc można go wczytać normalnie:
Utwórz trasę
main-page.js
:import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.store.find('main-stuff'); } });
Następnie możesz utworzyć odpowiedni szablon Kierownica
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>
Powiedzmy więc, że w swoim szablonie chcesz mieć osobne sekcje dotyczące relacji miłości / nienawiści z serem, z których każda (z jakiegoś powodu) jest poparta własnym modelem. Masz wiele rekordów w każdym modelu z obszernymi szczegółami dotyczącymi każdego powodu, jednak chcesz, aby zawartość na górze była szybko renderowana. Tutaj pojawia się
{{render}}
pomocnik. Możesz zaktualizować swój szablon w następujący sposób:<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>
Teraz musisz utworzyć kontrolery i szablony dla każdego. Ponieważ w tym przykładzie są identyczne, użyję tylko jednego.
Utwórz kontroler o nazwie
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); } } });
Zauważysz, że używamy
PromiseProxyMixin
tutaj, co sprawia, że kontroler jest świadomy obietnic. Podczas inicjalizacji kontrolera wskazujemy, że obietnica powinna ładowaćlove-cheese
model za pośrednictwem Ember Data. Musisz ustawić tę właściwość we właściwości kontrolerapromise
.Teraz utwórz szablon o nazwie
love-cheese.hbs
:{{#if isPending}} <p>Loading...</p> {{else}} {{#each item in promise._result }} <p>{{item.reason}}</p> {{/each}} {{/if}}
W swoim szablonie będziesz mógł renderować różne treści w zależności od stanu obietnicy. Po pierwszym załadowaniu strony zostanie wyświetlona sekcja „Powody, dla których kocham ser”
Loading...
. Kiedy obietnica zostanie załadowana, wyświetli wszystkie przyczyny związane z każdym rekordem twojego modelu.Każda sekcja wczytuje się niezależnie i nie blokuje natychmiastowego renderowania głównej treści.
To jest uproszczony przykład, ale mam nadzieję, że wszyscy uznają go za równie przydatny, jak ja.
Jeśli chcesz zrobić coś podobnego dla wielu wierszy treści, powyższy przykład Novelys może okazać się jeszcze bardziej odpowiedni. Jeśli nie, powyższe powinno działać dobrze.
źródło
Może to nie jest najlepsza praktyka i naiwne podejście, ale koncepcyjnie pokazuje, w jaki sposób można by mieć dostęp do wielu modeli na jednej centralnej trasie:
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); } });
mam nadzieję, że to pomoże
źródło
Dzięki wszystkim innym znakomitym odpowiedziom stworzyłem mixin, który łączy najlepsze rozwiązania tutaj w prosty i wielokrotnego użytku interfejs. Wykonuje
Ember.RSVP.hash
inafterModel
dla określonych modeli, a następnie wstrzykuje właściwości do kontrolera wsetupController
. Nie koliduje ze standardowymmodel
zaczepem, więc nadal możesz to zdefiniować jako normalne.Przykładowe zastosowanie:
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') } } });
Oto 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); } });
źródło
https://stackoverflow.com/a/16466427/2637573 jest odpowiedni dla powiązanych modeli. Jednak w najnowszej wersji Ember CLI i Ember Data istnieje prostsze podejście do niepowiązanych modeli:
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) }); } });
Jeśli chcesz pobrać tylko właściwość obiektu
model2
, użyj DS.PromiseObject zamiast 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')) }); } });
źródło
post
trasę / widok edycji, w którym chcę wyrenderować wszystkie istniejące tagi w bazie danych, aby użytkownik mógł kliknąć, aby dodać je do edytowanego postu. Chcę zdefiniować zmienną, która reprezentuje tablicę / kolekcję tych tagów. Czy podejście, które zastosowałeś powyżej, zadziała w tym przypadku?PromiseArray
(np. „Tagi”). Następnie w swoim szablonie przekażesz go do elementu wyboru odpowiedniego formularza.Dodając do odpowiedzi MilkyWayJoe, przy okazji:
this.store.find('post',1)
zwroty
{ id: 1, title: 'string', body: 'string string string string...', author_id: 1, comment_ids: [1, 2, 3, 6], tag_ids: [3,4] };
autor byłby
{ id: 1, firstName: 'Joe', lastName: 'Way', email: '[email protected]', points: 6181, post_ids: [1,2,3,...,n], comment_ids: [1,2,3,...,n], }
komentarze
{ id:1, author_id:1, body:'some words and stuff...', post_id:1, }
... Uważam, że powiązania są ważne, aby nawiązać pełną relację. Mam nadzieję, że to komuś pomoże.
źródło
Możesz użyć haków
beforeModel
lub,afterModel
ponieważ są one zawsze nazywane, nawet jeślimodel
nie są wywoływane, ponieważ używasz segmentów dynamicznych.Zgodnie z dokumentacją routingu asynchronicznego :
Więc powiedz, że masz
themes
nieruchomość na swoimSiteController
, możesz mieć coś takiego: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')); }
źródło
this
wewnątrz obietnicy dałoby komunikat o błędzie. Możesz ustawićvar _this = this
przed powrotem, a następnie_this.set(
zastosowaćthen(
metodę, aby uzyskać pożądany efekt