👏 Great work!
So just earlier today, I was mentioning the async component use case to @rwjblue and @ef4.
A friend was asking for ideas on how to implement this, and he mentioned something along the lines of "you almost want a component with its own mini-route". That sounded bizarre at first, but as I think about it a bit more, it is starting to make a lot of sense.
Of course, a "route" for a component doesn't make any sense. What we actually want is the "almost free" asynchrony handling and state management offered by the Ember router. Async and states are much more generic than the problem of routing, and since this approach worked out so well for routing, it makes a lot sense that you would desire the same kind of mechanisms in other places that you need to deal with these problems.
So, I think I would be interested in something along the lines of http://emberjs.jsbin.com/bujufi/1/edit. Basically, like the router, you have a promise hook (I would have called it model like the router, but that clashes with the property that holds the actual resolved value) where you can optionally implement and return a promise. If you did return a promise from the hook, it will put the component into the loading substate and render the component-name/loading template, if there is one. When the promise resolves, it will assign the resolved value to the model property and render the regular component-name template. If it rejects, it will render the component-name/error.
This is not a formal proposal, of course, and there are many nuances to be addressed. But overall, I feel that it fits in quite naturally with the rest of the framework.
I would also like to draw your attention to this part of the code:
App.YelpReviewComponent = Ember.Component.extend({ businessId: null, // Passed in promise: function() { if (this.get('businessId')) { return GetYelpReview( this.get('businessId') ); } else { return null; } }.property('businessId'), ... });
The highlight is that the promise hook is actually just a regular computed property, so you just declare its dependencies like you normally would. When one or more of its dependencies changed, this will "re-fire", and the framework can just observe this computed property to determine whether the component has changed state.
To me, this feels very natural and aligns very well with other parts of the framework (unlike refreshModel, etc, which are special-occasion concepts that I need to learn).
Once we have asynchronous components, it may be tempting to handle
all data loading through them, eliminating traditionalRoutes, and
essentially moving themodelhook ontoComponents.
Is this really so bad?
My gut feeling is that the routable component use case might just be a special case of async components. I think data-fetching async components should actually a pretty quite common thing, and the reason we aren't thinking/implementing them this way today is mostly just a side-effect of the poor ergonomics. If you think about it, <img> <audio> <video> <iframe> etc are all data-fetching async components – with window.fetch coming to browsers this should feel much more natural and second-nature over time.
So, I think it would need to have an a solid API for async components regardless. Since people need to learn/do that anyway, it is perhaps not a bad idea at all to put most of this responsibility (async management, substates) in the component, and implement routing in terms of async components. Most people coming from server-side MVC have NFI what a "route" object is (to be honest, I still don't know if I understand what it /really/ is, as opposed to just understanding how to use them), so I think it is not necessarily a bad thing if you can get away with not doing much at all with them in the 80% common cases.
I am completely thinking out loud here, so bullshit alert on please. I feel like I am not smart enough to put together all the pieces, but there seems to be some interesting stuff in there somewhere. Substates, for example, are interesting. Since they don't actually change the URLs anyway (and in fact, we iterated a few times on whether they should eagerly update the URLs and stuff like that), it perhaps suggest that it is not intrinsically a routing concern. The CP + dependencies promise hook is also interesting. Then perhaps {{outlet}}s are really just {{yield}}s (now you can yield variables, callbacks etc, perhaps it would get rid of some of the controllerFor/modelFor global-punch-through awkwardness). Maybe all the route need to do is just instantiate the WhateverRouteComponent, set all the params as attributes on it and let it figure out what to do (promise, substates and whatnot). Perhaps the component (or links, etc) will call setState on the component to change between its child states/routes/components/templates, and the router just observe
these states to change URL accordingly...
Anyway. I don't really have much concrete suggestions at the moment, and definitely haven't considered any backwards compatibility things at all. Just thought that I would throw this out there and let people smarter than me decide if it's pure snake oil or what 😄
I should also mention, it's almost 5AM here, so take it with many grains of salt 🙊