javascriptmongodbmeteoriron-routermeteor-helper

Meteor + MongoDB: How to get nested data?


I'm new to Meteor and trying to figure out this issue I have.

I'm trying to load data from the Lessons collection based on the route being passed. e.g if /courses/level1/lesson1/1a is passed then show data

Unfortunately this doesn't work.

Am I on the right path or is there a better way of doing this?

Collection

{
  "_id": "YSgr3fvjpEBn7ncRa",
  "courseId": "level1",
  "lesson": [
    {
      "lessonId": "lesson1",
      "freeLesson": true,
      "title": "Lesson 1",
      "eachLesson": [
        {
          "eachLessonId": "1a",
          "title": "This is (masculine)",
          "video": "839843"
        },
        {
          "eachLessonId": "1b",
          "title": "That is (masculine)",
          "video": "839843"
        },
        {
          "eachLessonId": "1c",
          "title": "This is (feminine)",
          "video": "839843"
        },
        {
          "eachLessonId": "1d",
          "title": "That is (feminine)",
          "video": "839843"
        },
        {
          "eachLessonId": "1e",
          "title": "Getting to know you",
          "video": "839843"
        }
      ]
    }
  ]
}

Routes

 Router.route("courses/:courseId/:lessonId/:eachLessonId", {
  path:"/courses/:courseId/:lessonId/:eachLessonId",
  layoutTemplate: "layoutLessons",
  template:"lessons",
  onBeforeAction:function(){
    var currentUser = Meteor.userId();
    if (currentUser) {
      Session.set('courseId', this.params.courseId);
      Session.set('lessonId', this.params.lessonId);
      Session.set('eachLessonId', this.params.eachLessonId);
      this.next();
     } else {
       Router.go('/')
     }
  },
});

Template helper

Template.lessons.onCreated(function(){
  Meteor.subscribe('listLessons');
});


Template.lessons.helpers({
  currentLesson: function() {
    var currentLesson = Session.get('eachLessonId');
    return Lessons.find({"lesson.eachLesson.eachLessonId" : currentLesson});
  },
});

HTML

{{#each currentLesson}}
  {{title}}
  {{video}}
{{/each}}

Solution

  • Instead of storing courseId, lessonId and eachLessonId as Session values, you could use the Iron Router's waitOn and data option.

    For example, you could rewrite your route as follows:

    Router.route('/courses/:courseId/:lessonId/:eachLessonId', {
      name: 'lessons',
      layoutTemplate: 'layoutLessons',
      template: 'lessons',
      onBeforeAction: function() {
        let currentUser = Meteor.user();
        if (currentUser) this.next();
        else Router.go('/');
      },
      data: function() {
        var doc = Lessons.findOne({
          "courseId": this.params.courseId,
          "lesson.lessonId": this.params.lessonId,
          "lesson.eachLesson.eachLessonId": this.params.eachLessonId
        });
        if (doc) {
          var lesson = {};
          var lessonId = this.params.eachLessonId;
          _.each(doc.lesson, function(i) {
            lesson = _.find(i.eachLesson, function(j) {
              return j.eachLessonId == lessonId;
            });
          });
          return lesson;
        }
        return {};
      },
      waitOn: function() {
        return [
          Meteor.subscribe('lesson', this.params.courseId, this.params.lessonId, this.params.eachLessonId)
        ];
      }
    });
    

    This should set the data context to the requested eachLesson object. However, you may consider setting the data context to a document in the Lessons collection and then just picking certain eachLesson objects. In addition, you should create a publish function which returns just the requested Lessons document and not all of them, like you probably do now in your listLessons publication. You can pass all IDs as arguments to the corresponding publish function.