Start Date | Relevant Team(s) | RFC PR |
---|---|---|
2020-03-26 |
Ember.js, Ember CLI |
We propose a low-level way to designate a component as the target of a route as an alternative to implicitly rendering a template from the app/templates
directory.
Today, a route's template file is located in the app/templates
directory; with modern components having co-located templates, this results in templates existing under two directories.
In the past, others have requested making components routable. For various reasons, the RFC for routable components is closed. Now that Ember has modernized its syntax and has fully adopted Glimmer components, adding a low-level construct that can make it possible to use a component to render a route should help create a pathway to satisfy the needs of those whom desired routable components.
We can't talk about routes and components without addressing controllers, which some have long considered to be an oddity in Ember. At present day, controllers have their place, but can be confusing because they live in a space between the behavior of a component and a route. A function called setRouteComponent
would allow many apps to get away without controllers if desired.
This isn't to say that setRouteComponent
is intended to replace controllers. Rather, it would help the community come to a more conclusive answer on whether controllers are here to stay.
If controllers are done away with one day, it could also lead to fewer concepts that someone new to Ember would have to learn. If it eventually turns out that components can serve the role of controllers, then that's one thing that developers would no longer have to wrap their heads around.
Since components can exist as template-only, without a JS file, the ability to use them to render routes is appealing because it can allow the developer to keep their templates together within the same parent directory of app/components
. Today, we're stuck having two directories that contain templates: app/components
and app/templates
. Conceptually, having a single directory related to UI templates should be easier to understand for many developers.
Allowing route to render in a component may also have the advantage of allowing routes to be integration-tested without the need to boot an entire Ember app or require the setup of a router.
In frontend libraries such as React, components are used in conjunction with a router to render pages – thus supporting similar functionality would allow a workflow familiar to developers coming from other disciplines.
We propose to introduce the following low-level APIs:
Similar to setComponentTemplate, the setRouteComponent
function takes two arguments, the first being the component class, the second being the route class. It transparently associates the given component class with the route class in a way can be retrieved later with the getRouteComponent
function described
below. For convenience, setRouteComponent
will return the route class (the second argument).
Once a component is associated with a route, the route will no longer lookup a template by name under app/templates
.
The getRouteComponent
function takes a route class and returns the component associated with the given route class, if any, or one of its superclasses, if any, or undefined
if no component association was found.
When a component is invoked by a route, the model
and controller
are passed as arguments.
// app/routes/foo.js
import Route from '@ember/routing/route';
import { setRouteComponent } from 'ember-route-component';
import BarComponent from '../components/bar';
export default FooRoute extends Route {
model() {
return this.store.findQuery('user', 123);
}
}
setRouteComponent(BarComponent, FooRoute);
Although not specifically called for by this RFC, it might make sense to support decorator syntax in conjunction with plain function-call syntax. This could be worthwhile given the relatively low-cost of implementation.
// app/routes/foo.js
import Route from '@ember/routing/route';
import { setRouteComponent } from 'ember-route-component';
import BarComponent from '../components/bar';
@setRouteComponent(BarComponent)
export default FooRoute extends Route {
model() {
return this.store.findQuery('user', 123);
}
}
In the case that a developer wants to delegate a component to a loading or error route substate, they would do so the same way they would with a normal route. The obvious difference is that a component delegated to a substate wouldn't receive a model
argument.
Note that a substate that uses a component would always need a JS file as well as any files that implement the component, unlike substates that can be a single template file at present. While this is a bit disadvantageous, it's conceivable that one can share a single component between multiple substates.
- How would the use of
{{outlet}}
be handled with components? - Is this something to be used directly by the end-developer, or should it only be available for the purpose of a higher-level mechanism?
- How can someone customize arguments that are passed to a component? (e.g. something like a
setupComponent
route hook) This may determine how underlying aspects of this feature are worked out. - Will components be invoked immediately when a route is activated, or should they wait for the
model
hook?