No Data in hasMany Relationship On Initial Load


#1

ember-cli - 3.20, ember-data - 3.30

I am trying to modify the data in a hasMany relationship in the controller setup but the relationship has no data. However, all the data is there after the page is fully loaded (i.e. in my template/actions, all relationship data is there)

I have a Quiz application with Many-Many relationship with Questions.

models/Quiz.js

import { computed } from '@ember/object';
import DS from 'ember-data';

const { attr, hasMany, Model } = DS;

export default Model.extend({
  description: attr('string'),
  questions: hasMany('question', {async: true}) //also tried with async false

});

models/Question.js

export default Model.extend({
  question: attr('string'),
  quizzes: hasMany('quiz', {async: true}) //also tried with async false
});

Go to url ‘/quiz/1’ and Route calls findRecord on quiz

routes/quizzes/quiz.js

import Route from '@ember/routing/route';

export default Route.extend({
  model(params) { return this.store.findRecord('quiz', params.quiz_id); }
});

controllers/quizzes/quiz.js

import { computed } from '@ember/object';
import Controller from '@ember/controller';

export default Controller.extend({
  quiz: computed.alias('model'),

  modelChanged: function() {
    let quiz = this.get('quiz');
    let questions = quiz.get('questions'); //questions has no data
    questions.then(questions => {
      Promise.all(questions.map(question => {
        //modify questions/answers here
      }));
    });
  }.observes('model')

actions: {
  getQuestions() {
    let questions = this.get('quiz.questions');  //questions now has data
  }
})};

I have tried to get the question data in both setupController() and afterModel() with no luck.

What has worked:
The quizzes are nested routes able to select between each quiz to display. So if you navigate from ‘/quiz/1’ to ‘/quiz/2’ and then back to ‘quiz/1’, the question data is available in the observer, setupController, afterModel, etc. So, the second time you access a specific quiz, the data is available in setup. (data is always available in template/actions).

Any ideas?


#2

Temporary Workaround:

Use an observer on ‘quiz.questions’ along with a flag to check if first time hitting observer.

import { computed } from '@ember/object';
import Controller from '@ember/controller';

export default Controller.extend({
  quiz: computed.alias('model'),

  areAnswersSet: false,

  observeQuestions: function() {
    let questions = this.get('quiz.questions');
    if (!this.areAnswersSet && questions.length !== 0) {
      this.toggleProperty('areAnswersSet');
      questions.forEach(question => { //modify question });
    }
  }.observes('quiz.questions.[]')

Drawback: Observer will still get called on every questions change. Only needed on initial load.


#3

Since it seems like your route really needs to have both the quiz and all its questions loaded before the route can really do anything useful, you can make sure both load in the model() hook so that you don’t have to deal with asynchrony anywhere else:

model({ quiz_id }) {
  return this.store.findRecord('quiz', quiz_id).then(quiz => {
    return quiz.hasMany('questions').load().then(() => {
      return quiz;
    });
  });
}

I would recommend combining this with

  questions: hasMany('question', {async: false})

since you’re already taking responsibility to decide when the questions should load, and you want quiz.get('questions') to just always immediately give you back the already-loaded questions, and not trigger some additional request.

If you can get include support from your backend the model hook can simplify to:

model({ quiz_id }) {
  return this.store.findRecord('quiz', quiz_id, { include: 'questions' });
}

and it will probably be faster because its one round trip instead of two.


#4

Still the same issue, on first hit of route, questions aren’t loaded. I am getting

router.js:929 Error while processing route: quizzes.quiz quiz.hasMany(…).load(…).then is not a function

But after first hit, subsequent hits load the questions properly.


#5

I’d put a breakpoint on return quiz.hasMany('questions').load() and try to find what’s wrong there.

If quiz is indeed a Quiz record and the questions relationship is defined on it, then quiz.hasMany('questions') should return a DS.HasManyReference which does have a load method.

If you turn your relationship into a synchronous one (by defining { async: false }), which I suggest you do, you’ll have to call reload instead of load on the reference as ED considers sync relationships to be loaded.


#6

Maybe you’re using an older version of Ember Data that predates the references API?