EmberJS / Ember Data에서 단일 경로로 여러 모델을 사용하는 방법은 무엇입니까?


85

문서를 읽어 보면 다음과 같이 경로에 모델을 할당해야하는 것처럼 보입니다.

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

특정 경로에서 여러 개체를 사용해야하는 경우 어떻게합니까? 즉, 게시물, 댓글 및 사용자? 로드 할 경로를 어떻게 지정합니까?

답변:


117

영원히 마지막 업데이트 : 계속 업데이트 할 수 없습니다. 따라서 이것은 더 이상 사용되지 않으며 이런 식으로 될 것입니다. 여기 스레드 더 최신 더 나은, 그리고 어떻게 같은 노선에 여러 모델을로드 : EmberJS은?

업데이트 : 원래 대답 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) 만로드 Post나는 정의 할 수 있습니다 고려하고, 그 길에서 모델을 DS.hasMany협회 Comment모델과 DS.belongsTo에 대한 연결을 User모두에 CommentPost모델. 이 같은:

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

이 정의는 다음과 같이 생성됩니다.

모델 간의 연결

이 정의를 사용하면 내가 find게시물을 올릴 때마다 해당 게시물과 관련된 댓글 모음, 댓글 작성자 및 게시물 작성자 인 사용자에 액세스 할 수 있습니다 . 경로는 간단합니다.

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

그래서에서 PostRoute(또는 PostsPostRoute당신이 사용하는 경우 resource), 내 템플릿에 액세스 할 수 있습니다 컨트롤러의 content는 IS, 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>

( 바이올린 참조 )


관련없는 모델에 대한 답변 :

그러나 시나리오가 설명 된 것보다 조금 더 복잡 하거나 특정 경로에 대해 다른 모델을 사용 (또는 쿼리) 해야하는 경우에서 수행하는 것이 좋습니다 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));
    }
});

이제 Post 경로에있을 때 템플릿이 후크 user에 설정된대로 컨트롤러 의 속성에 액세스 할 수 있습니다 setupController.

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

( 바이올린 참조 )


15
주셔서 감사합니다 너무 많은 것을 게시 할 시간을내어, 나는 그것이 정말 유용하다고.
Anonymous

1
@MilkyWayJoe, 정말 좋은 게시물입니다! 이제 내 접근 방식은 정말 순진 해 보입니다. :)
직관적

관련없는 모델의 문제는 모델 후크처럼 약속을 수락하지 않는다는 것입니다. 이에 대한 해결 방법이 있습니까?
miguelcobain 2014.01.13

1
문제를 올바르게 이해하면 쉽게 수정할 수 있습니다. 약속이 이행 될 때까지 기다린 다음 모델을 컨트롤러의 변수로 설정하십시오
Fabio

댓글을 반복하고 표시하는 것 외에 다른 사람이 새 댓글을 post.comments.
Damon Aw

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

자세한 내용은 https://github.com/tildeio/rsvp.js 를 확인하십시오 Em.RSVP.


그러나 경로에 동적 세그먼트가있는 경우 Em.Object또는 Em.RSVP솔루션을 사용하지 마십시오.

주요 문제는 link-to. link-to모델로 생성 된 링크를 클릭하여 URL을 변경 하면 모델이 해당 경로로 직접 전달됩니다. 이 경우 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}}

여기서 상황이 흥미로워집니다. URL /post/1을 사용 하여 페이지를 방문하면 model후크가 호출되고 setupControllerpromise가 해결되면 일반 객체를 가져옵니다.

하지만 link-to링크 를 클릭하여 페이지를 방문하면 post모델을PostRoute 하고 경로는 model후크 를 무시합니다 . 이 경우 setupController얻을 것이다 post물론 사용자를 얻을 수의 모델을.

따라서 동적 세그먼트가있는 경로에서 사용하지 마십시오.


2
내 대답은 이전 버전의 Ember & Ember-Data에 적용됩니다. 이것은 정말 좋은 접근 방식입니다 +1
MilkyWayJoe 2013 년

11
실제로 있습니다. 모델 자체가 아닌 모델 ID를 도우미에 연결하려면 모델 후크가 항상 트리거됩니다.
darkbaby123 2013-12-28

2
이것은 Ember 가이드의 어딘가에 문서화되어야합니다 (모범 사례 등). 많은 사람들이 접하게 될 중요한 사용 사례입니다.
yorbro

1
사용하는 경우 controller.setProperties(model);컨트롤러에 기본값으로 이러한 속성을 추가하는 것을 잊지 마십시오. 그렇지 않으면 예외가 발생합니다Cannot delegate set...
마테우스 노박

13

잠시 동안을 사용 Em.RSVP.hash하고 있었지만 문제는 렌더링 전에 모든 모델이로드 될 때까지 뷰를 기다리지 않으려는 것입니다. 그러나 Ember 를 사용하는 Novelys 의 사람들 덕분에 훌륭한 (그러나 상대적으로 알려지지 않은) 솔루션을 찾았습니다. .

세 가지 시각적 섹션이있는 뷰가 있다고 가정 해 보겠습니다. 이러한 각 섹션은 자체 모델에 의해 뒷받침되어야합니다. 보기 상단의 "splash"콘텐츠를 뒷받침하는 모델은 작고 빠르게로드되므로 정상적으로로드 할 수 있습니다.

경로 만들기 main-page.js:

import Ember from 'ember';

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

그런 다음 해당 Handlebars 템플릿을 만들 수 있습니다 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여기서는 컨트롤러가 약속을 인식하도록 만드는 여기를 사용하고 있음을 알 수 있습니다. 컨트롤러가 초기화되면 Promise가 love-cheeseEmber 데이터를 통해 모델을 로드해야 함을 나타냅니다 . 컨트롤러의 promise속성 에서이 속성을 설정해야 합니다.

이제 다음과 같은 템플릿을 만듭니다 love-cheese.hbs.

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

템플릿에서 약속 상태에 따라 다른 콘텐츠를 렌더링 할 수 있습니다. 페이지가 처음로드 될 때 "내가 치즈를 좋아하는 이유"섹션이 표시됩니다.Loading... . promise가로드되면 모델의 각 레코드와 관련된 모든 이유가 렌더링됩니다.

각 섹션은 독립적으로로드되며 주요 콘텐츠가 즉시 렌더링되는 것을 차단하지 않습니다.

이것은 단순한 예이지만 다른 모든 사람들이 나처럼 유용하다고 생각하기를 바랍니다.

여러 행의 콘텐츠에 대해 유사한 작업을 수행하려는 경우 위의 Novelys 예제가 훨씬 더 관련성이 있음을 알 수 있습니다. 그렇지 않은 경우 위의 내용이 잘 작동합니다.


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

4

다른 모든 훌륭한 답변 덕분에 여기 최고의 솔루션을 간단하고 재사용 가능한 인터페이스로 결합하는 믹스 인을 만들었습니다. 지정한 모델 Ember.RSVP.hashafterModel대해 in 을 실행 한 다음 속성을의 컨트롤러에 삽입합니다 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);
  }
});

2

https://stackoverflow.com/a/16466427/2637573 은 관련 모델에 적합합니다. 그러나 최신 버전의 Ember CLI 및 Ember Data를 사용하면 관련없는 모델에 대한 더 간단한 접근 방식이 있습니다.

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.PromiseArray 대신 DS.PromiseObject를 사용합니다 .

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

나는이 post내가 사용이 게시물 편집중인에 추가하기 위해 클릭 할 수 있도록 DB의 모든 기존의 태그를 렌더링 할 편집 경로 /보기를. 이러한 태그의 배열 / 컬렉션을 나타내는 변수를 정의하고 싶습니다. 위에서 사용한 접근 방식이 이것에 효과가 있습니까?
Joe

물론입니다 PromiseArray(예 : "태그"). 그런 다음 템플릿에서 해당 양식의 선택 요소로 전달합니다.
AWM 2015

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: 'MilkyWayJoe@example.com',
    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,
}

... 나는 완전한 관계가 확립되도록 링크 백이 중요하다고 생각합니다. 누군가에게 도움이되기를 바랍니다.


0

당신은 사용할 수 beforeModel또는 afterModel경우에도이 항상 호출로 후크를 model동적 세그먼트를 사용하고 있기 때문에 호출되지 않습니다.

당으로 비동기식 라우팅 문서 :

모델 후크는 약속시 일시 중지 전환에 대한 많은 사용 사례를 다루지 만 때로는 관련 후크 beforeModel 및 afterModel의 도움이 필요합니다. 가장 일반적인 이유는 {{link-to}} 또는 transitionTo (URL 변경으로 인한 전환이 아님)를 통해 동적 URL 세그먼트가있는 경로로 전환하는 경우 경로에 대한 모델이 're transitioning into will 이미 지정되었습니다 (예 : {{# link-to'article 'article}} 또는 this.transitionTo ('article ', article)).이 경우 모델 후크가 호출되지 않습니다. 이러한 경우 라우터가 전환을 수행하기 위해 모든 경로의 모델을 수집하는 동안 모든 로직을 수용하기 위해 beforeModel 또는 afterModel 후크를 사용해야합니다.

따라서에 themes속성 이 있다고 가정하면 SiteController다음과 같은 것을 가질 수 있습니다.

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
this약속 내부를 사용 하면 오류 메시지가 나타날 것이라고 생각 합니다. var _this = this반환 전에 설정 한 다음 원하는 결과를 얻기 위해 메서드 _this.set(내부 를 수행 then(할 수 있습니다.
Duncan Walker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.