Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setRouteComponent API #731

Closed
wants to merge 5 commits into from
Closed
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions text/0731-set-route-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
Stage: Accepted
Start Date: 2021-03-25
Release Date: Unreleased
Release Versions:
ember-source: vX.Y.Z
ember-data: vX.Y.Z
Relevant Team(s): Framework
RFC PR: https://github.com/emberjs/rfcs/pull/731
---

# Add `setRouteComponent`

## Summary

Adds an API for associating a component with a route. When used, the associated
component will be rendered instead of the route's template, allowing Ember
apps to use new features such as template imports and strict mode in whole
applications.

```js
import Route, { setRouteComponent } from '@ember/routing/route';
import MyRouteComponent from './component';

export default class MyRoute extends Route {}

setRouteComponent(MyRouteComponent, MyRoute);
```

## Motivation

Recently, Ember added support for strict mode, which enables the usage of
template imports:

```js
import Component from '@glimmer/component';
import { setComponentTemplate } from '@ember/component';
import { precompileTemplate } from '@ember/template-compilation';
import MyButton from './my-button';

export default class Example extends Component {}

setComponentTemplate(
precompileTemplate(
`<MyButton/>`,
{
strictMode: true,
scope() {
return { MyButton };
}
}
),
Example
);
```

This example demonstrates how to use the new low level APIs to do this. Clearly,
this isn't the final API that Ember users should write in their apps, as it's
very verbose overall, but it's the first step toward defining a new
programming model which will come together in a future edition. We have,
essentially, entered the pit of incoherence, which means that some of these
features will not be fully complete or ready for mainstream use. Template
imports are such a feature, partially because we don't have a final authoring
format, but also partially because you cannot use them currently with a very
important part of Ember: Routes.

There currently is not a way to directly set the template of a route the same
way that you can set the template of a component with `setComponentTemplate`.
This means you cannot associate a template defined with `precompileTemplate`
with a route, and so there is no way to use imported values with them. Instead,
components must still be defined in the `app/components` directory, and then
used via resolution in a route's template defined in `app/templates/routes`.

This RFC seeks to add a method for associating a _component_ with a route. This
will allow users to associate strict mode templates with a route via a
component - either a template only component or one with a backing class. This
will allow the community to continue experimenting with different template
import formats more thoroughly, and will also allow users to experiment with
building Ember apps without controllers by effectively bypassing them.
Controllers will not be fully removed, as they still have some functionality
which has not been replaced, but this will help to see where those gaps are and
figure out what is needed to fill them.

This takes us one step further into the pit of incoherence, with the goal of
better defining how to move forward in two major areas that we are aiming for in
the next peak of coherence.

## Detailed design

This RFC proposes adding the `setRouteComponent` API, importable from
`@ember/routing/route`.

```js
declare function setRouteComponent(component: Component, route: Route): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some patterns I have seen is

articles
  list
  detail

and articles.hbs is just an {{outlet}}.

How strict is this API to only include a component? Could handlebars expressions be allowed as the first argument?

Copy link

@Ravenstine Ravenstine Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How strict is this API to only include a component? Could handlebars expressions be allowed as the first argument?

Whether or not this fits in to the current proposal, I think this is a really cool idea! I've found it useful in the past to be able to have all a route's assets (templates and controllers) come from one JS file.

This is something I've achieved before by overriding the resolver so that it would look for a template named export from a route.

Being able to use setRouteComponent to do something similar would be pretty awesome, even if it required a separate createComponent function to maintain the semantics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea with ember-template-imports is that an hbs expression is a template-only component, and using them together you could definitely do this in practice:

import { hbs } from 'ember-template-imports';
import Route, { setRouteComponent } from '@ember/routing/route';

export default class MyRoute extends Route {}

setRouteComponent(hbs`Hello, world!`, MyRoute);

If we find that it still makes sense to associate a template that is not a component with a route in the future, then we can add that functionality either via a separate API, or via this API. My hope is that we can start to do the opposite though, lowering the distinction between templates and components even more in the future. I would like, for instance, for hbs used in tests to actually be inline-template-only-components, rather than just templates.

Copy link
Contributor

@chriskrycho chriskrycho Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ravenstine note that this is a low-level API on which exactly those kinds of things would be built. In the same way that setComponentTemplate allows us to add ember-template-imports syntax on top of a component definition (rather than using setComponentTemplate directly, this API is the necessary building block to allow you to do something similar in the future.

Worth note, though, that it wouldn't be exactly like that, because the backing class for a component is not a Route. You'd always want to be attaching a component definition, whether that's a template-only component or a class-backed component. With template imports and this API, though, you can easily imagine being able to do something like this, in a hypothetical future where we did add the decorator approach you suggested in the main thread:

import Route, { withComponent } from '@ember/routing/route';
import Component from '@glimmer/component';

const MyRouteView = 
  <template>
    <div>{{@model.someProp}}</div>
  </template>

@withComponent(MyRouteView)
export default class MyRoute extends Route {
  model(params, transition) {
    // ...
  }
}

(Note that I'm not suggesting I want that specifically; just noting that the point is having the appropriate set of primitives that lest us build and experiment with a variety of things in this space.)

As a closely related point, @snewcomer we wouldn't want this to accept strings there, but with strict mode we can straightforwardly use any tool which produces a strict-mode component, so you can imagine just passing <template>{{outlet}}</template> there (or the equivalent with hbs) and it would Just Work™.

Edit: to clarify re: "strings", basically what @pzuraq said. We were apparently typing simultaneously. 😂

```

Users can associate any component definition with a route via
`setRouteComponent`. When a component has been associated with a route this way,
the route will render this component at its root rather than rendering the route
template defined in `app/templates/routes`. The route's `render` and
`renderTemplate` hooks will also be ignored, and will not be run as they would
have previously.

The component will be passed the `@model` and `@controller` arguments, which
correspond to the model returned from the route's `model` hook and the instance
of the route's controller.

This will effectively be syntactic sugar for rendering a top-level component
within a route template today:

```js
import Route, { setRouteComponent } from '@ember/routing/route';
import MyRouteComponent from './component';

export default class MyRoute extends Route {}

setRouteComponent(MyRouteComponent, MyRoute);
```

Is equivalent to having the following in the `my-route.hbs` file:

```hbs
<MyRouteComponent @model={{@model}} @controller={{this}} />
```

Assuming that `MyRouteComponent` is resolvable via that name.

No changes will be made to the route or controller lifecycle, hooks, or
behaviors in general, since this is not related to the goal of unlocking
experimentation with strict mode and route templates, or the goal of
experimenting with controller-less applications. Changes in these areas will be
done in future RFCs instead.
Comment on lines +171 to +175
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One unanswered question I thought of here: how does this interact with the existing abilities to muck with specific rendering patterns on routes, render and renderTemplate? Is using those with this a hard error, a deprecation, a warning, etc.? And what's the actual behavior—is renderTemplate ignored if using setRouteComponent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, we should address that. I believe these APIs would be ignored, since we are fully ignoring the route's template in general. Since these APIs are deprecated, I think this also won't introduce any inconsistencies.


## How we teach this

For the time being, this API will not be the recommended way of writing Ember
apps. As such, it will not be included in the guides.
Comment on lines +206 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for calling this out


### API Docs

`setRouteComponent` can be used to associate a component with a route. When
associated, this component will be rendered instead of the route's template. The
component receives the `@model` and `@controller` arguments, which correspond
to the model returned from the route's `model` hook and the instance of the
route's controller.

```js
import Route, { setRouteComponent } from '@ember/routing/route';
import MyRouteComponent from './component';

export default class MyRoute extends Route {}

setRouteComponent(MyRouteComponent, MyRoute);
```

Is equivalent to having the following in the `my-route.hbs` file:

```hbs
<MyRouteComponent @model={{@model}} @controller={{this}} />
```

Assuming that `MyRouteComponent` is resolvable via that name.

## Drawbacks

The primary drawback is that this new API increases incoherence in the short
term, since there will now be multiple ways to define templates for routes.

## Alternatives

- We could introduce an API for setting the template of a route directly,
`setRouteTemplate`. This would still require users to use controllers as the
backing context for the template, and the API becomes a bit awkward at that
point - should you associate the template with the route, or the controller?