EmberJS: Wie lade ich mehrere Modelle auf derselben Route?

75

Während ich in der Webentwicklung nicht neu bin, bin ich in clientseitigen MVC-Frameworks ziemlich neu. Ich habe einige Nachforschungen angestellt und beschlossen, es mit EmberJS zu versuchen. Ich habe den TodoMVC-Leitfaden durchgesehen und es ergab für mich einen Sinn ...

Ich habe eine sehr einfache App eingerichtet. Indexroute, zwei Modelle und eine Vorlage. Ich habe ein serverseitiges PHP-Skript ausgeführt, das einige Datenbankzeilen zurückgibt.

Eine Sache, die mich sehr verwirrt, ist das Laden mehrerer Modelle auf derselben Route. Ich habe einige Informationen zur Verwendung eines setupControllers gelesen, bin mir aber noch nicht sicher. In meiner Vorlage befinden sich zwei Tabellen, die ich mit nicht verwandten Datenbankzeilen laden möchte. In einer traditionelleren Web-App hätte ich nur SQL-Anweisungen ausgegeben und diese durchlaufen, um die Zeilen zu füllen. Ich habe Schwierigkeiten, dieses Konzept in EmberJS zu übersetzen.

Wie lade ich mehrere Modelle nicht verwandter Daten auf derselben Route?

Ich verwende die neuesten Ember- und Ember Data-Bibliotheken.

Aktualisieren

Obwohl die erste Antwort eine Methode zur Handhabung enthält, erklärt die zweite Antwort, wann sie angemessen ist, und die verschiedenen Methoden, wann sie nicht angemessen ist.

Eric
quelle
Beantwortet das deine Frage? Fordern Sie zwei Modelle zusammen an
Panagiotis Panagi

Antworten:

95

HINWEIS: Für Ember 3.16+ Apps ist hier derselbe Code, jedoch mit aktualisierter Syntax / Muster: https://stackoverflow.com/a/62500918/356849

Das Folgende gilt für Ember <3.16, obwohl der Code als 3.16+ als vollständig abwärtskompatibel funktionieren würde, aber es macht nicht immer Spaß, älteren Code zu schreiben.


Mit Ember.RSVP.hash können Sie mehrere Modelle laden:

app/routes/index.js

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return Ember.RSVP.hash({
      people: this.store.findAll('person'),
      companies: this.store.findAll('company')
    });
  },

  setupController(controller, model) {
    this._super(...arguments);
    Ember.set(controller, 'people', model.people);
    Ember.set(controller, 'companies', model.companies);
  }
});

Und in Ihrer Vorlage können Sie auf die geladenen Daten verweisen peopleund companiesdiese abrufen:

app/templates/index.js

<h2>People:</h2>
<ul>
  {{#each people as |person|}}
    <li>{{person.name}}</li>
  {{/each}}
</ul>
<h2>Companies:</h2>
<ul>
  {{#each companies as |company|}}
    <li>{{company.name}}</li>
  {{/each}}
</ul>

Dies ist ein Twiddle mit diesem Beispiel: https://ember-twiddle.com/c88ce3440ab6201b8d58

Marcio Junior
quelle
9
Dieser Ansatz ist in Ordnung, vorausgesetzt, Sie haben keine dynamischen Segmente in Ihrer Route. Wenn Sie dynamische Segmente haben und die Route über eingegeben wird, {{link-to 'index' someModel}}wird der Modellhaken vollständig übersprungen, wodurch dieses Beispiel unterbrochen wird. Ein robusterer Ansatz besteht darin, zusätzliche Modelle zu laden, in setupControllerdenen immer ausgeführt wird.
Eoin Kelly
Genau das habe ich gesucht!
PDXIII
1
@EoinKelly Ich würde hier tatsächlich das Controller # afterModel-Ereignis verwenden, da Sie ein Versprechen von afterModel zurückgeben und erwarten können, dass es sich wie ein Modell verhält, außer dass afterModel nicht übersprungen wird.
Samsinite
1
Wie greifen Sie auf das dynamische Segment, die Parameter oder die Abfrageparameter im Setupcontroller zu?
Ahnbizcad
1
@Eoin Kelly: Sie können es umgehen, indem Sie n die ID (oder Schnecke) anstelle des Modells übergeben
ahnbizcad
151

IN ACHT NEHMEN:

Sie möchten vorsichtig sein, ob die Rückgabe mehrerer Modelle in Ihrem Modellhaken angemessen ist. Stellen Sie sich diese einfache Frage:

  1. Lädt meine Route dynamische Daten basierend auf der URL mithilfe eines Slugs :id? dh this.resource('foo', {path: ':id'});

Wenn Sie mit Ja geantwortet haben

Versuchen Sie nicht, auf dieser Route mehrere Modelle vom Modellhaken zu laden !!! Der Grund liegt in der Art und Weise, wie Ember die Verknüpfung mit Routen handhabt. Wenn Sie beim Verknüpfen mit dieser Route ein Modell angeben ( {{link-to 'foo' model}}, transitionTo('foo', model)), wird der Modell-Hook übersprungen und das mitgelieferte Modell verwendet. Dies ist wahrscheinlich problematisch, da Sie mehrere Modelle erwartet haben, aber nur ein Modell ausgeliefert wird. Hier ist eine Alternative:

Mach es in setupController/afterModel

App.IndexRoute = Ember.Route.extend({
  model: function(params) {
    return $.getJSON('/books/' + params.id);
  },
  setupController: function(controller, model){
    this._super(controller,model);
    controller.set('model2', {bird:'is the word'});
  }
});

Beispiel: http://emberjs.jsbin.com/cibujahuju/1/edit

Wenn Sie es benötigen, um den Übergang zu blockieren (wie es der Modellhaken tut), geben Sie ein Versprechen vom afterModelHaken zurück. Sie müssen die Ergebnisse dieses Hooks manuell verfolgen und an Ihren Controller anschließen.

App.IndexRoute = Ember.Route.extend({
  model: function(params) {
    return $.getJSON('/books/' + params.id);
  },
  afterModel: function(){
    var self = this;
    return $.getJSON('/authors').then(function(result){
      self.set('authors', result);
    });
  }, 
  setupController: function(controller, model){
    this._super(controller,model);
    controller.set('authors', this.get('authors'));
  }
});

Beispiel: http://emberjs.jsbin.com/diqotehomu/1/edit

Wenn Sie mit Nein geantwortet haben

Lassen Sie uns mehrere Modelle vom Modell-Hook der Route zurückgeben:

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return {
           model1: ['red', 'yellow', 'blue'],
           model2: ['green', 'purple', 'white']
    };
  }
});

Beispiel: http://emberjs.jsbin.com/tuvozuwa/1/edit

Wenn darauf gewartet werden muss (z. B. ein Anruf beim Server, eine Art Versprechen)

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return Ember.RSVP.hash({
           model1: promise1,
           model2: promise2
    });
  }
});

Beispiel: http://emberjs.jsbin.com/xucepamezu/1/edit

Im Fall von Ember Data

App.IndexRoute = Ember.Route.extend({
  var store = this.store;
  model: function() {
    return Ember.RSVP.hash({
           cats: store.find('cat'),
           dogs: store.find('dog')
    });
  }
});

Beispiel: http://emberjs.jsbin.com/pekohijaku/1/edit

Wenn eines ein Versprechen ist und das andere nicht, ist alles gut, RSVP wird diesen Wert gerne nur verwenden

App.IndexRoute = Ember.Route.extend({
  var store = this.store;
  model: function() {
    return Ember.RSVP.hash({
           cats: store.find('cat'),
           dogs: ['pluto', 'mickey']
    });
  }
});

Beispiel: http://emberjs.jsbin.com/coxexubuwi/1/edit

Mix and Match und viel Spaß!

App.IndexRoute = Ember.Route.extend({
  var store = this.store;
  model: function() {
    return Ember.RSVP.hash({
           cats: store.find('cat'),
           dogs: Ember.RSVP.Promise.cast(['pluto', 'mickey']),
           weather: $.getJSON('weather')
    });
  }, 
  setupController: function(controller, model){
    this._super(controller, model);
    controller.set('favoritePuppy', model.dogs[0]);
  }
});

Beispiel: http://emberjs.jsbin.com/joraruxuca/1/edit

Kingpin2k
quelle
Wenn sich mein Modell basierend auf Abfrageparametern und nicht auf einem dynamischen Segment ändert, lautet meine Antwort dann Ja oder Nein?
Ahnbizcad
und wie übergebe ich Abfrageparameter an die Hooks beforeModel, afterModel und setController?
Ahnbizcad
2
Sie können immer noch mehrere Modelle haben und Ihre Links nicht beschädigen. Wenn Sie zwei dynamische Segmente haben, übergeben Sie eine ID (oder eine Zeichenfolge, wenn Sie Ihr Modell basierend auf einem Slug über den serialize:Hook in Ihrer Route erstellen ), anstatt ein Modell im Link zu übergeben. Zu model.model1.somePropertymodel.puppyModel.someOtherProperty
Ihrer Information,
Sie können sicher, dass die meisten der oben genannten Abfragen keine dynamischen Segmente sind und dass Sie die meiste Zeit nicht von verschiedenen Routen aus eine Reihe verschiedener Modelle auf einer anderen Route abrufen möchten, um die Links ordnungsgemäß einzurichten (einmal) wieder anwendbar auf nicht dynamische Modelle).
Kingpin2k
3

Ich benutze so etwas wie die Antwort, die Marcio gegeben hat, aber es sieht ungefähr so ​​aus:

    var products = Ember.$.ajax({
        url: api + 'companies/' +  id +'/products',
        dataType: 'jsonp',
        type: 'POST'
    }).then(function(data) {
        return data;
    });

    var clients = Ember.$.ajax({
        url: api + 'clients',
        dataType: 'jsonp',
        type: 'POST'
    }).then(function(data) {
        return data;
    });

    var updates = Ember.$.ajax({
        url: api + 'companies/' +  id + '/updates',
        dataType: 'jsonp',
        type: 'POST'
    }).then(function(data) {
        return data;
    });

    var promises = {
        products: products,
        clients: clients,
        updates: updates
    };

    return Ember.RSVP.hash(promises).then(function(data) {
      return data;
    });  
la_antorcha
quelle
Dies sieht nicht wie der Ember-Weg aus
Adria
Dies gibt eine Reihe von gelösten Versprechungen von Ember.RSVP zurück. Warum ist der Glut-Weg nicht so? Vielleicht nicht auf die Art von Glutdaten, aber ich habe keine Glutdaten verwendet. Sie können die Dokumente hier sehen: emberjs.com/api/classes/RSVP.Promise.html
la_antorcha
Das ist richtig, ich meinte den Ember Data Weg, ignoriere meinen ersten Kommentar
Adria
1
Dies funktioniert nicht wie erwartet. Die Logik in thenFunktionen macht keinen Unterschied. Sie vermasseln den asynchronen Code und kehren zurück.
Jelhan
2

Wenn Sie Ember Data verwenden, wird es für nicht verwandte Modelle noch einfacher:

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

Wenn Sie nur die Eigenschaft eines Objekts für abrufen möchtenmodel2 , verwenden Sie DS.PromiseObject anstelle von 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'))
    });
  }
});
AWM
quelle
2

Die neueste Version der JSON-API, wie sie in Ember Data v1.13 implementiert ist, unterstützt die Bündelung verschiedener Ressourcen in derselben Anforderung sehr gut, wenn Sie nichts dagegen haben, Ihre API-Endpunkte zu ändern.

In meinem Fall habe ich einen sessionEndpunkt. Die Sitzung bezieht sich auf einen Benutzerdatensatz, und der Benutzerdatensatz bezieht sich auf verschiedene Modelle, die immer geladen werden sollen. Es ist ziemlich schön, dass alles mit einer Anfrage eingeht.

Eine Einschränkung gemäß der Spezifikation ist, dass alle von Ihnen zurückgegebenen Entitäten in irgendeiner Weise mit der empfangenen primären Entität verknüpft sein sollten. Ich glaube, dass Glutdaten nur die expliziten Beziehungen durchlaufen, wenn der JSON normalisiert wird.

In anderen Fällen entscheide ich mich jetzt dafür, das Laden zusätzlicher Modelle zu verschieben, bis die Seite bereits geladen ist, dh für separate Datenfelder oder was auch immer, damit zumindest die Seite so schnell wie möglich gerendert wird. Dabei ist ein Verlust / eine Änderung des zu berücksichtigenden "automatischen" Fehlerladezustands zu verzeichnen.

Pik-Ass
quelle
2

Nehmen Sie die akzeptierte Antwort und aktualisieren Sie sie für Ember 3.16+

app/routes/index.js

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class IndexRoute extends Route {
  @service store;

  async model() {
    let [people, companies] = await Promise.all([
      this.store.findAll('person'),
      this.store.findAll('company'),
    ]);


    return { people, companies };
  }

}

Hinweis: Es wird empfohlen, setupController nicht zum Einrichten von Aliasen zu verwenden, da dadurch verschleiert wird, woher Daten stammen und wie sie von der Route zur Vorlage fließen.

In Ihrer Vorlage können Sie also Folgendes tun:

<h2>People:</h2>

<ul>
  {{#each @model.people as |person|}}
    <li>{{person.name}}</li>
  {{/each}}
</ul>

<h2>Companies:</h2>

<ul>
  {{#each @model.companies as |company|}}
    <li>{{company.name}}</li>
  {{/each}}
</ul>
NullVoxPopuli
quelle