2013-05-09 56 views
84

從閱讀文檔,它看起來像你必須(或應該)模型分配給路由,像這樣:如何在EmberJS/Ember Data中使用單一路徑的多個模型?

App.PostRoute = Ember.Route.extend({ 
    model: function() { 
     return App.Post.find(); 
    } 
}); 

如果我需要在一定的航線使用幾個對象?即帖子,評論和用戶?我如何告訴路線加載這些路線?

回答

115

末永遠更新:我無法繼續更新。所以這是不贊成的,可能會這樣。這裏有一個更好,更漂亮,更最新線程EmberJS: How to load multiple models on the same route?

更新:在我原來的答案,我說在模型定義中使用embedded: true。這是不正確的。在修訂版12中,Ember-Data期望外鍵用單個記錄的後綴(link_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)之間associations,只加載Post模型,這條路線,考慮到我可以在CommentPost模型中定義Comment模型的DS.hasMany關聯和User模型的DS.belongsTo關聯。事情是這樣的:

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一個帖子,我將有機會獲得與相關的評論集該帖子以及評論的作者,以及該帖子的作者的用戶,因爲它們都嵌入了。該航線保持簡單:

App.PostsPostRoute = Em.Route.extend({ 
    model: function(params) { 
     return App.Post.find(params.post_id); 
    } 
}); 
在​​

所以(或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's template 
     controller.set('user', App.User.find(window.user_id)); 
    } 
}); 

,現在當我在郵政路線是,因爲它是在setupController鉤設置我的模板將有機會獲得user財產控制器:

<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

+15

非常感謝*花了很多時間發佈,我發現它非常有用。 – Anonymous 2013-05-09 18:07:51

+1

@MilkyWayJoe,真的很棒!現在我的方法看起來很幼稚:) – intuitivepixel 2013-05-09 22:02:38

+0

你的非相關模型的問題是它不接受像模型鉤那樣的承諾,對吧? 有沒有解決方法? – miguelcobain 2014-01-13 11:33:27

8

這可能不是最好的做法,一個天真的方法,但它在概念上顯示你將如何去有一箇中央的路線可用於多個車型:

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); 
    } 
}); 

希望它有助於

+1

這種方法很好,只是沒有太多的Ember數據優勢。對於模型不相關的一些場景,我得到了類似的東西。 – MilkyWayJoe 2013-05-09 22:45:13

49

使用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.RSVPhttps://github.com/tildeio/rsvp.js


但不要用Em.ObjectEm.RSVP解決方案,如果您的路由具有動態段

的主要問題是link-to。如果您使用模型點擊由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}} 

事情變得有趣在這裏。當您使用網址/post/1訪問該頁面時,將調用model鉤子,並且當承諾解析時,setupController將獲得普通對象。

但是,如果你通過點擊訪問該頁面link-to鏈接,它傳遞post模型​​和路線會忽略model掛鉤。在這種情況下,setupController將得到post模型,當然你不能得到用戶。

因此,請確保您不要在具有動態細分的路線中使用它們。

+2

我的回答適用於早期版本的Ember&Ember-Data。這是一個非常好的方法+1 – MilkyWayJoe 2013-10-20 12:18:02

+0

因此,在動態細分中沒有解決方法? – beku8 2013-12-28 09:27:15

+11

其實有。如果你想傳遞模型id而不是模型本身來鏈接到helper,模型鉤子總是被觸發。 – darkbaby123 2013-12-28 11:11:36

0

您可以使用beforeModelafterModel鉤子,因爲這些鉤子總是被調用,即使沒有調用model因爲您正在使用動態段。

由於每asynchronous routing文檔:

模型鉤涵蓋了暫停上承諾的過渡許多用例,但有時你需要的相關掛鉤beforeModel和afterModel的幫助。最常見的原因是,如果您通過{{link-to}}或transitionTo(而不是由URL更改導致的轉換)過渡到包含動態網址段的路線,則路線模型'轉換成將已經被指定(例如{{#鏈接到'文章'文章}}或this.transitionTo('文章',文章)),在這種情況下,模型鉤子不會被調用。在這些情況下,您需要使用beforeModel或afterModel掛鉤來存放任何邏輯,而路由器仍然收集所有路由的模型以執行轉換。

所以說,你有你的SiteController一個themes屬性,你可以有這樣的事情:

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')); 
} 
+1

我認爲在promise中使用'this'會給出錯誤信息。你可以在返回之前設置'var _this = this',然後在'then'內執行'_this.set(''獲得所需結果的方法 – 2014-04-05 04:51:43

1

添加到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, 
} 

... 我相信,這樣建立了完整的關係鏈接背上有重要意義。希望能幫助別人。

4

感謝所有其他優秀的答案,我創建了一個mixin,將最好的解決方案結合到一個簡單且可重用的界面中。它對afterModel執行Ember.RSVP.hash的指定模型,然後將屬性注入控制器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); 
    } 
}); 
13

對於我是用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'); 
    } 
}); 

然後你就可以創建一個相應的把手模板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這裏,這使得控制許感知。當控制器初始化時,我們表明承諾應該通過Ember Data加載love-cheese模型。您需要在控制器的promise屬性上設置此屬性。

現在,創建一個名爲love-cheese.hbs模板:

{{#if isPending}} 
    <p>Loading...</p> 
{{else}} 
    {{#each item in promise._result }} 
    <p>{{item.reason}}</p> 
    {{/each}} 
{{/if}} 

在模板中,你就可以根據承諾的狀態來呈現不同的內容。當您的頁面初次加載時,您的「我愛吃芝士的原因」部分將顯示Loading...。當承諾被加載時,它將呈現與模型的每條記錄相關的所有原因。

每個部分將獨立加載,不會立即阻止主要內容的渲染。

這是一個簡單的例子,但我希望其他人都認爲它和我一樣有用。

如果您希望爲多行內容做類似的事情,您可能會發現上面的Novelys示例更加相關。如果沒有,上述應該適合你。

2

https://stackoverflow.com/a/16466427/2637573適用於相關型號。然而,隨着新版本的灰燼CLI和灰燼數據,對不相關的模型更簡單的方法:

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')) 
    }); 
    } 
}); 
+0

我有一個'post'編輯路徑/視圖,我想渲染所有在數據庫中的現有標籤,以便使用可以點擊將它們添加到正在編輯的帖子中我想定義一個變量來表示這些標籤的數組/集合您上面使用的方法是否適用於此? – Joe 2015-07-09 06:47:30

+0

當然,你會創建一個'PromiseArray'(例如「tags」),然後在你的模板中將它傳遞給相應表單的選擇元素。 – AWM 2015-07-09 12:44:07

相關問題