Skip to content

Instantly share code, notes, and snippets.

@gossi
Last active June 10, 2019 15:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gossi/2b062f014ff6411b056b53147a17d888 to your computer and use it in GitHub Desktop.
Save gossi/2b062f014ff6411b056b53147a17d888 to your computer and use it in GitHub Desktop.
Concept: Ember Pages vs Route/Controller

In response to: https://gos.si/blog/ember-2019-reduce-complexity/

The idea in short

  1. Keep routing as-is
  2. Delete Route and Controller
  3. Replace with Page
  4. Page extends a glimmer component with special sauce to handle transitions and (query) params

Imagine the following layout (yeah, I like MU):

Concept

src/
- ui/
  - components/
  - pages/
    - application/
      - page.ts
      - template.hbs
    - about/
      - page.ts
      - template.hbs
- routes.ts

Notes:

  • routes.ts continues to work as usual
  • a route leads to a page ! (this is semantically more correct than what we have atm, this drives me nuts 🙈)
  • The ember resolver will be able to find a page for a route (unless this is somewhat dynamically importable - Something I can truly not estimate properly! Hello Embroider 🤖)
  • a page.ts exports a Page class as default and has the following signature:
import Component from '@glimmer/component';

interface Page<T> extends Component<T> {

  queryParams: QueryParams; // whatever this will look like

  activate(transition: Transition);
  deactivate(transition: Transition);
}
  • this.args contains the params - handle them as you do in components :)
  • activate() and deactivate() let you handle the transitioning logic, they work asynchronously (something a constructor cannot - if you want to async load your model)

Example

Define your routes (same as today):

// routes.ts

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

Write your page (similar to what a component is, really cool - huh?):

// src/ui/pages/post/page.ts

import Page from '@ember/routing';
import { task, lastValue } from 'ember-concurrency-decorators';

interface PostParams {
  postId: string;
}

export default class PostPage extends Page<PostParams> {

  @lastValue('load')
  post: Post; // I didn't import this type - I know

  activate(/*transition: Transition*/) {
    this.load.perform();
  }
  
  @task
  load = function*(this: PostPage) {
    const response = yield fetch(`/api/post/${this.args.postId}`);
    return await response.toJson();
  };
}

And our template:

{{!-- src/ui/pages/post/tempalte.hbs --}}

{{#if this.load.isRunning}}
  Loading ...
{{else}}
  <Post @post{{this.post}}/>
{{/if}}

Caveats

I'm pretty sure there is plenty caveats, especially around sub-pages, etc. This gist more describes what the API for consumers can be.

Semantics

  • "But like, with pages, page Implies you only have one full thing at a time" @nullvoxpopuli
  • "you only have one full thing at a time" is the counter against page
  • The idea is to fill in the blank for this sentence: "A route leads to ___"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment