jelhan
jelhan

Reputation: 6338

Best practice to organize list, view, create and update routes in Ember.js

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?

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

Upvotes: 0

Views: 246

Answers (1)

Trenton Trama
Trenton Trama

Reputation: 4930

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

Upvotes: 4

Related Questions