EmberJS / Ember Data'da tek bir rota ile birden fazla model nasıl kullanılır?


85

Dokümanları okuduktan sonra, aşağıdaki gibi bir rotaya bir model atamanız gerekiyor (veya yapmanız gerekiyor) gibi görünüyor:

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

Ya belirli bir rotada birkaç nesne kullanmam gerekirse? yani Gönderiler, Yorumlar ve Kullanıcılar? Rotaya bunları yüklemesini nasıl söylerim?

Yanıtlar:


117

Sonsuza dek son güncelleme : Bunu güncellemeye devam edemiyorum. Yani bu kullanımdan kaldırıldı ve muhtemelen bu şekilde olacak. işte daha iyi ve daha güncel bir iş parçacığı EmberJS: Aynı rotaya birden fazla model nasıl yüklenir?

Güncelleme: Orijinal cevabımda embedded: truemodel tanımında kullanmamı söyledim . Bu yanlış. Revizyon 12'de Ember-Data, yabancı anahtarların tek kayıt veya toplama için bir son ek ( bağlantı ) ile tanımlanmasını bekler . Aşağıdakine benzer bir şey:_id_ids

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

Keman'ı güncelledim ve herhangi bir değişiklik olursa veya bu yanıtta verilen kodla ilgili daha fazla sorun bulursam bunu tekrar yapacağım.


İlgili modellerle cevaplayın:

Sizin tarif edilmektedir senaryosu için, ben güvenmek istiyorum ilişkilendirmeleri modelleri arasındaki (ayar embedded: true) ve sadece yük PostBen tanımlayabilirsiniz göz önüne alırsak bu rotada modeli DS.hasManyiçin dernek Commentmodeli ve DS.belongsToiçin dernek Userhem de Commentve Postmodeller. Bunun gibi bir şey:

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

Bu tanım aşağıdaki gibi bir şey üretecektir:

Modeller arasındaki ilişkiler

Bu tanımla, ne zaman findbir Gönderi yapsam, bu gönderi ile ilişkili bir yorum koleksiyonuna ve yorumun yazarına ve hepsi gömülü olduğu için gönderinin yazarı olan kullanıcıya erişime sahip olacağım . Rota basit kalır:

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

Yani içinde PostRoute(veya PostsPostRoutekullanıyorsanız resource), şablonlarım model contentolan denetleyiciye erişebilecek Post, böylece yazara basitçeauthor

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

( keman bakın )


İlgili olmayan modellerle cevaplayın:

Senin senaryo açıklanan olandan biraz daha karmaşıktır ve / veya Ancak, sahip belirli bir rota için kullanım (veya sorgu) farklı modellere, ben bunu yapmak için öneriyoruz Route#setupController. Örneğin:

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

Ve şimdi Gönderi yolundayken, şablonlarım kancada userayarlandığı için denetleyicideki özelliğe erişebilecek 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>

( keman bakın )


15
Bunu göndermek için zaman ayırdığınız için çok teşekkür ederim , gerçekten yararlı buldum.
Anonim

1
@MilkyWayJoe, gerçekten iyi gönderi! Şimdi yaklaşımım gerçekten saf görünüyor :)
sezgisel

İlişkili olmayan modellerinizdeki sorun, model kancasının yaptığı gibi vaatleri kabul etmemesidir, değil mi? Bunun için herhangi bir çözüm var mı?
miguelcobain

1
Sorununuzu doğru anlarsam, onu kolay bir şekilde uyarlayabilirsiniz. Sadece sözün yerine getirilmesini bekleyin ve ancak o zaman modelleri kontrolörün bir değişkeni olarak ayarlayın
Fabio

Yorumları yinelemek ve görüntülemekten başka, birisinin içine nasıl yeni bir yorum ekleyebileceğine dair bir örnek gösterebilirseniz çok güzel olacaktır post.comments.
Damon Aw

49

Em.ObjectBirden çok modeli kapsüllemek için kullanmak , tüm verileri modelkancaya almanın iyi bir yoludur . Ancak görünüm oluşturulduktan sonra tüm verilerin hazırlanmasını garanti edemez.

Başka bir seçenek kullanmaktır Em.RSVP.hash. Birkaç sözü bir araya getirir ve yeni bir söz verir. Yeni söz, tüm sözler çözüldükten sonra çözülürse. Ve setupControllersöz çözülene veya reddedilene kadar çağrılmaz.

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

Bu şekilde, görünüm oluşturmadan önce tüm modelleri alırsınız. Ve kullanmanın Em.Objectbu avantajı yok.

Diğer bir avantaj ise, söz veren ve olmayanı birleştirebilmenizdir. Bunun gibi:

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

Daha fazla bilgi edinmek için bunu kontrol edin Em.RSVP: https://github.com/tildeio/rsvp.js


Ancak rotanızın dinamik segmentleri varsa kullanmayın Em.Objectveya Em.RSVPçözümlemeyin

Asıl sorun şudur link-to. URL'yi link-tomodellerle oluşturulan tıklama bağlantısıyla değiştirirseniz , model doğrudan bu rotaya iletilir. Bu durumda modelkanca aranmaz ve setupControllersize modeli link-toverirsiniz.

Bir örnek şuna benzer:

Yol kodu:

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

Ve link-tokod, gönderi bir model:

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

Burada işler ilginç hale geliyor. /post/1Sayfayı ziyaret etmek için url kullandığınızda , modelkanca çağrılır ve setupControllersöz çözüldüğünde düz nesneyi alır.

Ancak sayfayı tıklayarak link-tobağlantıyla ziyaret ederseniz , postmodele geçer PostRouteve rota modelkancayı yok sayar. Bu durumda model setupControlleralacaksınız posttabii ki kullanıcı alamazsınız.

Bu nedenle, bunları dinamik segmentlere sahip rotalarda kullanmadığınızdan emin olun.


2
Cevabım Ember & Ember-Data'nın önceki bir sürümü için geçerli. Bu gerçekten iyi bir yaklaşım +1
MilkyWayJoe

11
Aslında var. yardımcıya bağlantı için model yerine model kimliğini geçirmek istiyorsanız, model kancası her zaman tetiklenir.
darkbaby123

2
Bu, Ember kılavuzlarında (en iyi uygulamalar veya başka bir şey) bir yerde belgelenmelidir. Eminim birçok insanın karşılaştığı önemli bir kullanım durumu.
yorbro

1
Eğer kullanıyorsanız controller.setProperties(model);bu özellikleri varsayılan değer ile kontrolcüye eklemeyi unutmayınız. Aksi takdirde istisna atarCannot delegate set...
Mateusz Nowak

13

Bir süredir kullanıyordum Em.RSVP.hash, ancak karşılaştığım sorun, görünümümün render etmeden önce tüm modellerin yüklenmesini beklemesini istemememdi. Bununla birlikte, Novelys'teki dostlar sayesinde Ember.PromiseProxyMixin'i kullanmayı içeren harika (ancak nispeten bilinmeyen) bir çözüm buldum :

Diyelim ki üç farklı görsel bölüme sahip bir görünümünüz var. Bu bölümlerin her biri kendi modeli ile desteklenmelidir. Görünümün üst kısmındaki "sıçrama" içeriğini destekleyen model küçüktür ve hızlı bir şekilde yüklenir, böylece onu normal şekilde yükleyebilirsiniz:

Bir rota oluşturun main-page.js:

import Ember from 'ember';

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

Ardından uygun bir Gidon şablonu oluşturabilirsiniz 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>

Öyleyse, şablonunuzda peynirle olan aşk / nefret ilişkiniz hakkında her biri (herhangi bir nedenle) kendi modeliyle desteklenen ayrı bölümler olmasını istediğinizi varsayalım. Her modelde, her nedene ilişkin kapsamlı ayrıntılara sahip çok sayıda kayıt var, ancak üstteki içeriğin hızlı bir şekilde oluşturulmasını istiyorsunuz. {{render}}Yardımcının devreye girdiği yer burasıdır . Şablonunuzu şu şekilde güncelleyebilirsiniz:

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

Şimdi her biri için denetleyiciler ve şablonlar oluşturmanız gerekecek. Bu örnek için etkili bir şekilde özdeş olduklarından, sadece birini kullanacağım.

Şu adla bir kontrolör oluşturun 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);
        }
    }
});

PromiseProxyMixinDenetleyiciyi sözünün farkında kılan, burayı kullandığımızı fark edeceksiniz . Denetleyici başlatıldığında, sözün love-cheesemodeli Ember Data aracılığıyla yüklemesi gerektiğini belirtiriz . Bu özelliği denetleyicinin özelliğinde ayarlamanız gerekir promise.

Şimdi, şu adda bir şablon oluşturun love-cheese.hbs:

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

Şablonunuzda, sözün durumuna bağlı olarak farklı içerikler oluşturabileceksiniz. Sayfanız ilk yüklendiğinde, "Peyniri Sevme Sebepleri" bölümünüz görüntülenir Loading.... Söz yüklendiğinde, modelinizin her kaydı için ilişkili tüm nedenleri işleyecektir.

Her bölüm bağımsız olarak yüklenecek ve ana içeriğin hemen oluşturulmasını engellemeyecektir.

Bu basit bir örnek, ama umarım herkes onu benim kadar faydalı bulur.

Birçok içerik satırı için benzer bir şey yapmak istiyorsanız, yukarıdaki Novelys örneğini daha da alakalı bulabilirsiniz. Değilse, yukarıdakiler sizin için iyi çalışacaktır.


8

Bu, en iyi uygulama ve naif bir yaklaşım olmayabilir, ancak kavramsal olarak, tek bir merkezi rota üzerinde birden çok modelin mevcut olmasını nasıl sağlayacağınızı gösterir:

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

Umarım yardımcı olur


1
Bu yaklaşım iyidir, Ember Data'dan çok fazla yararlanmamak. Modellerin birbiriyle ilişkili olmadığı bazı senaryolar için buna benzer bir şey aldım.
MilkyWayJoe

4

Diğer tüm mükemmel yanıtlar sayesinde, buradaki en iyi çözümleri basit ve yeniden kullanılabilir bir arayüzde birleştiren bir mixin oluşturdum. Belirttiğiniz modeller için bir Ember.RSVP.hashgiriş çalıştırır afterModel, ardından özellikleri içindeki denetleyiciye enjekte eder setupController. Standarda müdahale etmezmodel kancayı , bu yüzden bunu yine de normal olarak tanımlarsınız.

Örnek kullanım:

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

İşte karışım:

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 , ilgili modeller için uygundur. Bununla birlikte, Ember CLI ve Ember Data'nın son sürümünde, ilgisiz modeller için daha basit bir yaklaşım vardır:

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

Eğer yalnızca bir nesnenin özelliğini almaya istiyorsanız model2, kullanmak DS.PromiseObject yerine 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'))
    });
  }
});

postDB'deki tüm mevcut etiketleri oluşturmak istediğim bir düzenleme yolum / görünümüm var, böylece kullanım onları düzenlenmekte olan gönderiye eklemek için tıklatabilir. Bu etiketlerin bir dizisini / koleksiyonunu temsil eden bir değişken tanımlamak istiyorum. Yukarıda kullandığınız yaklaşım bunun için işe yarar mı?
Joe

Elbette, bir PromiseArray(örneğin, "etiketler") oluşturursunuz. Ardından, şablonunuzda, onu ilgili formun seçim öğesine iletirsiniz.
AWM

1

MilkyWayJoe'nin cevabına ek olarak, teşekkürler btw:

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

İadeler

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

yazar olurdu

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

yorumlar

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

... Tüm ilişkinin kurulabilmesi için arka bağlantıların önemli olduğuna inanıyorum. Umarım bu birine yardımcı olur.


0

Dinamik segmentler kullandığınız için çağrılmasa bile, bunlar her zaman çağrıldıklarından beforeModelveya afterModelkancaları kullanabilirsiniz model.

Gereğince asenkron yönlendirme docs:

Model kancası, sözde duraklatma geçişleri için birçok kullanım durumunu kapsar, ancak bazen, ilgili kancaların yardımına, modelden önce ve modelden sonra ihtiyaç duyarsınız. Bunun en yaygın nedeni, {{link-to}} veya transitionTo (bir URL değişikliğinin neden olduğu bir geçişin aksine) aracılığıyla dinamik URL segmentli bir rotaya geçiş yapıyorsanız, 'ye geçiş zaten belirtilmiş olacaktır (örn. {{# bağlantı' makale 'makale}} veya this.transitionTo (' makale ', makale)), bu durumda model kancası çağrılmayacaktır. Bu durumlarda, yönlendirici bir geçiş gerçekleştirmek için yolun tüm modellerini toplarken, herhangi bir mantığı barındırmak için ya beforeModel ya da afterModel kancasını kullanmanız gerekir.

Diyelim ki üzerinde bir themesmülkünüz var, şöyle bir SiteControllerşeye sahip olabilirsiniz:

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
thisSöz içinde kullanmanın bir hata mesajı vereceğini düşünüyorum . Dönüşten var _this = thisönce ayarlayabilir ve ardından istenen sonucu elde etmek _this.set(için then(yöntemin içini yapabilirsiniz
Duncan Walker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.