Làm thế nào để sử dụng nhiều mô hình với một tuyến duy nhất trong Dữ liệu EmberJS / Ember?


85

Từ việc đọc tài liệu, có vẻ như bạn phải (hoặc nên) gán một mô hình cho một tuyến đường như sau:

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

Điều gì sẽ xảy ra nếu tôi cần sử dụng nhiều đối tượng trong một tuyến đường nhất định? tức là Bài viết, Bình luận và Người dùng? Làm cách nào để biết tuyến đường tải những thứ đó?

Câu trả lời:


117

Cập nhật lần cuối mãi mãi : Tôi không thể tiếp tục cập nhật cái này. Vì vậy, điều này không được dùng nữa và có thể sẽ là theo cách này. đây là một chủ đề hay hơn và cập nhật hơn EmberJS: Làm thế nào để tải nhiều mô hình trên cùng một tuyến đường?

Cập nhật: Trong câu trả lời ban đầu của tôi, tôi đã nói là sử dụng embedded: truetrong định nghĩa mô hình. Điều đó không chính xác. Trong bản sửa đổi 12, Ember-Data mong đợi các khóa ngoại được xác định bằng một hậu tố ( liên kết ) _idcho một bản ghi hoặc _idscho bộ sưu tập. Một cái gì đó tương tự như sau:

{
    id: 1,
    title: 'string',
    body: 'string string string string...',
    author_id: 1,
    comment_ids: [1, 2, 3, 6],
    tag_ids: [3,4]
}

Tôi đã cập nhật fiddle và sẽ thực hiện lại nếu có bất kỳ điều gì thay đổi hoặc nếu tôi phát hiện thêm vấn đề với mã được cung cấp trong câu trả lời này.


Trả lời với các mô hình liên quan:

Đối với kịch bản bạn đang mô tả, tôi sẽ dựa vào các liên kết giữa các mô hình (cài đặt embedded: true) và chỉ tải Postmô hình trong tuyến đó, xem xét tôi có thể xác định một DS.hasManyliên kết cho Commentmô hình và DS.belongsToliên kết cho Usercả mô hình CommentPostmô hình. Một cái gì đó như thế này:

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

Định nghĩa này sẽ tạo ra một cái gì đó như sau:

Sự liên kết giữa các mô hình

Với định nghĩa này, bất cứ khi nào tôi findlà một Bài đăng, tôi sẽ có quyền truy cập vào một bộ sưu tập các nhận xét được liên kết với bài đăng đó và cả tác giả của nhận xét cũng như người dùng là tác giả của bài đăng, vì tất cả chúng đều được nhúng . Lộ trình vẫn đơn giản:

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

Vì vậy, trong PostRoute(hoặc PostsPostRoutenếu bạn đang sử dụng resource), các mẫu của tôi sẽ có quyền truy cập vào bộ điều khiển content, đó là Postmô hình, vì vậy tôi có thể tham khảo tác giả, đơn giản là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>

(xem fiddle )


Trả lời với các mô hình không liên quan:

Tuy nhiên, nếu tình huống của bạn phức tạp hơn một chút so với những gì bạn đã mô tả và / hoặc phải sử dụng (hoặc truy vấn) các mô hình khác nhau cho một tuyến đường cụ thể, tôi khuyên bạn nên thực hiện nó Route#setupController. Ví dụ:

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

Và bây giờ khi tôi đang ở trong tuyến Post, các mẫu của tôi sẽ có quyền truy cập vào thuộc usertính trong bộ điều khiển như nó đã được thiết lập trong setupControllerhook:

<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>

(xem fiddle )


15
Cảm ơn bạn rất nhiều vì đã dành thời gian để đăng bài viết đó, tôi thấy nó thực sự hữu ích.
Ẩn danh

1
@MilkyWayJoe, bài rất hay! Bây giờ cách tiếp cận của tôi trông thực sự ngây thơ :)
Visualpixel

Vấn đề với các mô hình không liên quan của bạn là nó không chấp nhận những lời hứa như mô hình hook, phải không? Có giải pháp nào cho điều đó không?
miguelcobain

1
Nếu tôi hiểu đúng vấn đề của bạn, bạn có thể điều chỉnh nó một cách dễ dàng. Chỉ cần chờ đợi lời hứa thực hiện và chỉ sau đó thiết lập các mô hình (s) như là một biến của bộ điều khiển
Fabio

Ngoài việc lặp lại và hiển thị các nhận xét, sẽ rất tuyệt nếu bạn có thể hiển thị một ví dụ về cách ai đó có thể thêm một nhận xét mới vào post.comments.
Damon Aw,

49

Sử dụng Em.Objectđể đóng gói nhiều mô hình là một cách tốt để lấy tất cả dữ liệu trong modelhook. Nhưng nó không thể đảm bảo tất cả dữ liệu được chuẩn bị sau khi kết xuất chế độ xem.

Một sự lựa chọn khác là sử dụng Em.RSVP.hash. Nó kết hợp nhiều lời hứa với nhau và trả lại một lời hứa mới. Lời hứa mới nếu được giải quyết sau khi mọi lời hứa được giải quyết. Và setupControllerkhông được gọi cho đến khi lời hứa được giải quyết hoặc bị từ chối.

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

Bằng cách này, bạn có được tất cả các mô hình trước khi xem kết xuất. Và sử dụng Em.Objectkhông có lợi thế này.

Một lợi thế khác là bạn có thể kết hợp hứa và không hứa. Như thế này:

Em.RSVP.hash({
  post: // non-promise object
  user: // promise object
});

Kiểm tra phần này để tìm hiểu thêm về Em.RSVP: https://github.com/tildeio/rsvp.js


Nhưng không sử dụng Em.Objecthoặc Em.RSVPgiải pháp nếu tuyến đường của bạn có các đoạn động

Vấn đề chính là link-to. Nếu bạn thay đổi url bằng liên kết nhấp chuột được tạo bởi link-tocác mô hình, mô hình sẽ được chuyển trực tiếp đến tuyến đường đó. Trong trường hợp này, modelhook không được gọi và setupControllerbạn nhận được mô hìnhlink-to cung cấp cho bạn.

Một ví dụ như sau:

Mã tuyến:

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-tomã, bài đăng là một mô hình:

{{#link-to "post" post}}Some post{{/link-to}}

Mọi thứ trở nên thú vị ở đây. Khi bạn sử dụng url /post/1để truy cập trang, modelhook được gọi và setupControllernhận đối tượng đơn giản khi giải quyết lời hứa.

Nhưng nếu bạn truy cập trang bằng link-toliên kết nhấp chuột , nó sẽ chuyển postmô hình đến PostRoutevà tuyến đường sẽ bỏ qua modelhook. Trong trường hợp setupControllernày sẽ nhận được postmô hình, tất nhiên bạn không thể có được người dùng.

Vì vậy, hãy đảm bảo rằng bạn không sử dụng chúng trong các tuyến đường có phân đoạn động.


2
Câu trả lời của tôi áp dụng cho phiên bản trước đó của Ember & Ember-Data. Đây là một cách tiếp cận thực sự tốt 1
MilkyWayJoe

11
Trên thực tế là có. nếu bạn muốn chuyển id mô hình thay vì chính mô hình đến trình trợ giúp liên kết đến, thì móc mô hình luôn được kích hoạt.
darkbaby123 28/12/13

2
Điều này nên được ghi lại ở đâu đó trên các hướng dẫn của Ember (các phương pháp hay nhất). Đó là một trường hợp sử dụng quan trọng mà tôi chắc rằng nhiều người gặp phải.
yorbro

1
Nếu bạn đang sử dụng, controller.setProperties(model);đừng quên thêm các thuộc tính này với giá trị mặc định vào bộ điều khiển. Nếu không nó sẽ ném ngoại lệCannot delegate set...
Mateusz Nowak

13

Trong một thời gian tôi đã sử dụng Em.RSVP.hash, tuy nhiên vấn đề tôi gặp phải là tôi không muốn chế độ xem của mình phải đợi cho đến khi tất cả các mô hình được tải trước khi hiển thị. Tuy nhiên, tôi đã tìm thấy một giải pháp tuyệt vời (nhưng tương đối chưa được biết đến) nhờ những người ở Novelys liên quan đến việc sử dụng Ember.PromiseProxyMixin :

Giả sử bạn có một dạng xem có ba phần hình ảnh riêng biệt. Mỗi phần này nên được hỗ trợ bởi mô hình riêng của nó. Mô hình hỗ trợ nội dung "giật gân" ở đầu chế độ xem nhỏ và sẽ tải nhanh chóng, vì vậy bạn có thể tải mô hình đó bình thường:

Tạo một tuyến đường main-page.js:

import Ember from 'ember';

export default Ember.Route.extend({
    model: function() {
        return this.store.find('main-stuff');
    }
});

Sau đó, bạn có thể tạo một mẫu Handlebars tương ứng 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>

Vì vậy, giả sử trong mẫu của bạn, bạn muốn có các phần riêng biệt về mối quan hệ yêu / ghét của bạn với pho mát, mỗi phần (vì một số lý do) được hỗ trợ bởi mô hình riêng của nó. Bạn có nhiều bản ghi trong mỗi mô hình với các chi tiết mở rộng liên quan đến từng lý do, tuy nhiên bạn muốn nội dung trên cùng hiển thị nhanh chóng. Đây là nơi người {{render}}trợ giúp đi vào. Bạn có thể cập nhật mẫu của mình như vậy:

<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>

Bây giờ bạn sẽ cần tạo bộ điều khiển và mẫu cho mỗi bộ điều khiển. Vì chúng giống hệt nhau trong ví dụ này, tôi sẽ chỉ sử dụng một.

Tạo một bộ điều khiển có tên 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);
        }
    }
});

Bạn sẽ nhận thấy rằng chúng tôi đang sử dụng PromiseProxyMixinở đây, điều này làm cho bộ điều khiển nhận biết lời hứa. Khi bộ điều khiển được khởi tạo, chúng tôi chỉ ra rằng lời hứa sẽ tải love-cheesemô hình thông qua Dữ liệu Ember. Bạn sẽ cần đặt thuộc tính này trên thuộc tính của bộ điều khiển promise.

Bây giờ, hãy tạo một mẫu có tên love-cheese.hbs:

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

Trong mẫu của bạn, bạn sẽ có thể hiển thị nội dung khác nhau tùy thuộc vào trạng thái của lời hứa. Khi trang của bạn tải lần đầu, phần "Lý do tôi thích phô mai" của bạn sẽ hiển thịLoading... . Khi lời hứa được tải, nó sẽ hiển thị tất cả các lý do liên quan đến mỗi bản ghi của mô hình của bạn.

Mỗi phần sẽ tải độc lập và không chặn nội dung chính hiển thị ngay lập tức.

Đây là một ví dụ đơn giản, nhưng tôi hy vọng mọi người khác thấy nó hữu ích như tôi đã làm.

Nếu bạn đang muốn làm điều gì đó tương tự cho nhiều hàng nội dung, bạn có thể thấy ví dụ Novelys ở trên thậm chí còn phù hợp hơn. Nếu không, những điều trên sẽ phù hợp với bạn.


8

Đây có thể không phải là phương pháp hay nhất và là một cách tiếp cận ngây thơ, nhưng nó cho thấy về mặt khái niệm bạn sẽ làm như thế nào khi có nhiều mô hình có sẵn trên một tuyến đường trung tâm:

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

hy vọng nó giúp


1
Cách làm này ổn, chỉ là không tận dụng quá nhiều Ember Data. Đối với một số tình huống mà các mô hình không liên quan, tôi sẽ làm một cái gì đó tương tự như thế này.
MilkyWayJoe

4

Cảm ơn tất cả các câu trả lời xuất sắc khác, tôi đã tạo một mixin kết hợp các giải pháp tốt nhất ở đây thành một giao diện đơn giản và có thể tái sử dụng. Nó thực thi một Ember.RSVP.hashtrong afterModelcho các mô hình bạn chỉ định, sau đó đưa các thuộc tính vào bộ điều khiển trong đó setupController. Nó không can thiệp vào modelhook tiêu chuẩn , vì vậy bạn sẽ vẫn xác định điều đó như bình thường.

Ví dụ sử dụng:

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

Đây là 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);
  }
});

2

https://stackoverflow.com/a/16466427/2637573 là tốt cho các mô hình liên quan. Tuy nhiên, với phiên bản gần đây của Ember CLI và Ember Data, có một cách tiếp cận đơn giản hơn cho các mô hình không liên quan:

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

Nếu bạn chỉ muốn truy xuất thuộc tính của một đối tượng model2, hãy sử dụng DS.PromiseObject thay vì 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'))
    });
  }
});

Tôi có một postlộ trình / chế độ xem chỉnh sửa nơi tôi muốn hiển thị tất cả các thẻ hiện có trong DB để người sử dụng có thể nhấp để thêm chúng vào bài đăng đang được chỉnh sửa. Tôi muốn xác định một biến đại diện cho một mảng / tập hợp các thẻ này. Cách tiếp cận bạn đã sử dụng ở trên có hiệu quả cho việc này không?
Joe,

Chắc chắn, bạn sẽ tạo một PromiseArray(ví dụ: "thẻ"). Sau đó, trong mẫu của bạn, bạn sẽ chuyển nó đến phần tử lựa chọn của biểu mẫu tương ứng.
AWM

1

Thêm vào câu trả lời của MilkyWayJoe, cảm ơn btw:

this.store.find('post',1) 

trả lại

{
    id: 1,
    title: 'string',
    body: 'string string string string...',
    author_id: 1,
    comment_ids: [1, 2, 3, 6],
    tag_ids: [3,4]
};

tác giả sẽ là

{
    id: 1,
    firstName: 'Joe',
    lastName: 'Way',
    email: 'MilkyWayJoe@example.com',
    points: 6181,
    post_ids: [1,2,3,...,n],
    comment_ids: [1,2,3,...,n],
}

bình luận

{
    id:1,
    author_id:1,
    body:'some words and stuff...',
    post_id:1,
}

... Tôi tin rằng các liên kết ngược là quan trọng để mối quan hệ đầy đủ được thiết lập. Hy vọng rằng sẽ giúp một ai đó.


0

Bạn có thể sử dụng beforeModelhoặc afterModelhook vì chúng luôn được gọi, ngay cả khi modelkhông được gọi vì bạn đang sử dụng phân đoạn động.

Theo tài liệu định tuyến không đồng bộ :

Mô hình hook bao gồm nhiều trường hợp sử dụng cho quá trình chuyển đổi tạm dừng theo lời hứa, nhưng đôi khi bạn sẽ cần sự trợ giúp của các hook liên quan beforeModel và afterModel. Lý do phổ biến nhất cho điều này là nếu bạn đang chuyển đổi sang một tuyến đường có phân đoạn URL động qua {{link-to}} hoặc chuyển đổiTo (trái ngược với chuyển đổi do thay đổi URL), mô hình cho tuyến đường bạn 're transition into will đã được chỉ định (ví dụ: {{# link-to' article 'article}} hoặc this.transitionTo (' article ', article)), trong trường hợp đó, hook model sẽ không được gọi. Trong những trường hợp này, bạn sẽ cần sử dụng hook beforeModel hoặc afterModel để chứa bất kỳ logic nào trong khi bộ định tuyến vẫn đang thu thập tất cả các mô hình của tuyến đường để thực hiện chuyển đổi.

Vì vậy, giả sử bạn có một themestài sản trên của mình SiteController, bạn có thể có một cái gì đó như thế này:

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
Tôi nghĩ rằng việc sử dụng thisbên trong lời hứa sẽ đưa ra một thông báo lỗi. Bạn có thể thiết lập var _this = thistrước sự trở lại và sau đó làm _this.set(bên trong then(phương pháp để có được những kết quả mong muốn
Duncan Walker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.