0

Working for a few years with ember.js now, it's still not quite clear to me, what should be considered as best practice for structuring list, view, create and update routes.

The projects I've worked with so far mostly used to routing trees per entity. The pluralized entity name for listing with a subroute for create and the singular entity name for detail view with a subroute for editing. As an example a post model would have these for routes: /posts for listing posts, /posts/new for the create functionality, /post/:post_id for showing a single post and /post/:post_id/edit for editing that one. The corresponding router would look like this one:

Router.map(function() {
  this.route('post', { path: '/post/:post_id' }, function() {
    this.route('edit');
  });
  this.route('posts', function() {
    this.route('new');
  });
});

This approach is working quite nicely for detail and edit view cause they are sharing the same model. So the model hook of the edit route could just reuse the model of the detail view route. In ember code this looks like the following:

// app/routes/post.js
import Route from '@ember/routing/route';

export default Route.extend({
  model({ post_id }) {
    return this.get('store').findRecord('post', post_id);
  }
});

// app/routes/post/edit.js
import Route from '@ember/routing/route';

export default Route.extend({
  model() {
    return this.modelFor('post');
  }
});

Normally we would return a collection of posts from posts route model hook and not implementing the model hook of posts.new route (or returning a POJO / Changeset there depending on architecture but that's not the question here). Assuming we are not implementing the model hook of posts.new the routes would look like:

// app/routes/posts.js
import Route from '@ember/routing/route';

export default Route.extend({
  model({ post_id }) {
    return this.get('store').findAll('post');
  }
});

// app/routes/posts/new.js
import Route from '@ember/routing/route';

export default Route.extend({
});

But now this approach is not working well anymore cause a transition to posts.new route is blocked until the collection of posts are loaded. Since we don't need this collection to create a list of posts (at least if we only show them in posts.index route and not on all subroutes) this doesn't feel right.

Side note for those ones not that familiar with ember: Nested routes model hooks are executed in order. So in our case first the model hook of application route, afterwards posts route and then posts.new route waiting for any promise executed by one of them.

So what should then be considered as best practice?

  • Should the fetching of posts live in posts.index route if we are not showing them on nested routes?
  • Shouldn't the create route be a nested under the list route? So should we have posts, post-new, post and post.edit routes? Feels confusing since the post related code is splited over three route trees. Also it would go against the goal of the improved file layout being developed currently since the code would be splitted over three directories.
  • Should we just take the tradeoff of unnecessarily fetching the collection of posts since mostly the user flow comes from this route before the creation route and therefore the model hook is in most cases already loaded anyway?

Would appreciate any thoughts on that one. Decided to not ask that question in the community slack to better document the answer.

jelhan
  • 6,149
  • 1
  • 19
  • 35
  • If you are concerned about traffic usage, I'd suggest to fetch collection of posts in `posts.index` route. Also, I prefer `/posts`, `/posts/:post_id` and `/posts/new` (so all model-related urls start with pluralized model name) – Gennady Dogaev Jun 23 '18 at 12:33

1 Answers1

4

The main point of having a nested route in ember is to nest the output of your child route within the parent route. While your current structure works, it doesn't really match up with how ember has structured route functionality.

You should use a singular nested route with an explicitly defined index route.

At every level of nesting (including the top level), Ember automatically provides a route for the / path named index. To see when a new level of nesting occurs, check the router, whenever you see a function, that's a new level.

Router.map(function() {
  this.route('posts', function() {
    this.route('favorites');
  });
});

is equivalent to

Router.map(function() {
  this.route('index', { path: '/' });
  this.route('posts', function() {
    this.route('index', { path: '/' });
    this.route('favorites');
  });
});

If you create an explicit posts/index.js file, this can be used as your list route. Doing this will help your avoid the issue where all posts are fetched before transitioning into the create route.

While different from the structure you currently have, I'd suggest the following.

Router.map(function() {
  this.route('posts', function() {
      this.route('index');  // /posts  - posts/index.js
      this.route('new');    // /posts/new - posts/new.js
      this.route('view', { path: '/:post_id' }  // /posts/1234 - posts/view.js
      this.route('edit', { path: '/:post_id/edit' }  // /posts/1234/edit - posts/edit.js
  }); 
});

Depending on the complexity of logic in the new and edit, you can consider combining the two routes into one, or simply transitioning the new to edit after generating the empty model.

The benefits of this include:

Simplicity You don't have to re-define your paths for all of the routes. Everything falls under posts/ and the route specifies the next piece.

Consistency the JSONapi schema uses plural routes for both fetching a collection as well as a singular object.

Logic wrapping If, you use and explicit index.js file, you can use the old posts.js file as a common wrapper for all items within the post namespace. Posts.js will have an outlet that the index, new, edit, and view routes are placed into

If you need your view and edit route to share the same model generation, you can nest your view/edit into a common group so they share a parent model.

this.route('posts', function() {
    this.route('post', { path: '/:post_id' }, function() {
        this.route('view', { path: '/' }) ;
        this.route('edit', { path: '/edit' });
    })
})
Trenton Trama
  • 4,890
  • 1
  • 22
  • 27
  • Thanks for detailed answer. Which route would be responsible in loading the post record which is shared between view and edit routes? Or would you duplicate that code? In general: You are suggesting a structure that organizes the routes along the nesting of UI and not taking the model into account. Did I got that right? – jelhan Jun 23 '18 at 10:05
  • Right - Ember's routing is structured around the nesting of UI and I try to stick to that. A model normally isn't very complex to generate, so the duplication of code isn't a lot. However, if you do need that, you can nest view/edit together. I've updated my answer to describe that a little – Trenton Trama Jun 23 '18 at 12:40