Aurelia: [RFC]: (re-)naming templating and router lifecycle hooks to something more intuitive

Created on 16 Nov 2018  路  11Comments  路  Source: aurelia/aurelia

馃挰 RFC

Speaking from the perspective of vNext, we have the following lifecycle hooks/methods currently defined in templating:

Hooks

These are methods that users can define on their component classes (custom elements and custom attributes), and the framework will invoke them at appropriate times if and only if they are defined:

  • render, created (during $hydrate)
  • binding, bound (before/after $bind)
  • unbinding, unbound (before/after $unbind)
  • attaching, attached (before/after $attach)
  • detaching, detached (before/after $detach)
  • caching (before $cache)
  • flush (during processFlushQueue, after enqueueFlush)

Internal methods

These can be called by users in more advanced situations but in most cases end users will not deal with them directly. I'm listing them here because they're relevant in the bigger picture of avoiding ambiguity:

  • $hydrate (custom elements/attributes -> initialize component)
  • $bind, $unbind (custom elements/attributes, views -> enqueue hook and mount callbacks)
  • $attach, $detach (custom elements/attributes, views -> enqueue hook and mount callbacks)
  • $mount, $unmount (custom elements, views -> add to DOM)
  • $cache (custom elements/attributes, views)
  • hold, release (views -> set/unset RenderLocation)
  • flush (anything with batching such as observers, resources)
  • patch (bindings -> update targets)
  • connect (AST -> initiate observation)
  • observeProperty (bindings -> called by connect)
  • startObserving (observers -> called indirectly after the first subscribe)

Router hooks

We have not named these for the router yet. In vCurrent they are:

  • canActivate, activate
  • canDeactivate, deactivate

馃敠 Suggestions

Main candidates for rename are: attach/detach (and pre/post hooks) as well as the router lifecycle hooks.

We're already planning to "get rid of" connect (in the AST) by renaming it to observe (since that's what it does). This also opens up the option of renaming attach to connect.

Rename templating hooks attach -> connect, keep router hooks as-is

This would be consistent with WebComponents

  • Rename [attaching, $attach, attached to [connecting, $connect, connected]
  • Rename [detaching, $detach, detached] to [disconnecting, $disconnect, disconnected]
  • Keep [canActivate, activate, canDeactivate, deactivate] as-is

Switch templating hooks attach -> mount, keep router hooks as-is

This moves the ambiguity of attach/activate to the less visible internal api

  • Rename [attaching, $attach, attached] to [mounting, $mount, mounted]
  • Rename [detaching, $detach, detached] to [unmounting, $unmount, unmounted]
  • Rename existing [$mount, $unmount] to [$attach, $detach]
  • Keep [canActivate, activate, canDeactivate, deactivate] as-is

Rename router hooks activate -> enter

  • Rename [canActivate, activate] to [canEnter, enter]
  • Rename [canDeactivate, deactivate] to [canLeave, leave]

Rename router hooks activate -> navigateTo

  • Rename [canActivate, activate] to [canNavigateTo, navigateTo]
  • Rename [canDeactivate, deactivate] to [canNavigateAway, navigateAway]

Any other suggestions/variations? Looking for opinions from the community as well as core team members.

API In discussion Needs more feedback RFC Lifecycle

Most helpful comment

In the end - and perhaps I haven't made this clear enough in my previous comment - nothing fundamentally changes about the framework. @EisenbergEffect also pointed this out in Q2 report I believe. The syntax and conventions stay the same.

Here's the main changes:

  1. Class and method names, API signatures change/move
  2. Various timings change from async to sync
  3. Many internal implementation details that add extra power, capabilities, control and performance, but don't necessarily change what's ultimately rendered by the browser for any given application code base.

Backwards compatibility

We can address 1. with a v1-compat package that adds various aliases and adapters. vNext can be made to expose the same API as vCurrent. This is possible because vNext can do more than vCurrent, not less.

Point 2. is in itself not a problem because many queueMicroTask calls (which will be added to vNext via a compat layer) are simply not necessary anymore. This doesn't mean that this code will break per se. It just won't be the most optimal code.

The true breaking changes are the other way around. Things will be possible in vNext that aren't possible in vCurrent. It would be a one-way upgrade path once you start tapping into those extra capabilities. But that's reasonable, right?

We need your help to identify important APIs and use cases

Obviously, adding a v1-compat package means extra work and testing. It's not realistic to blatantly try and make every nook and cranny of the vCurrent API's work in vNext as well. That would beat the purpose of "vNext".
We need to know which API's (classes, methods, decorators) are most commonly used so we can focus our efforts on making those work (and testing them properly).

Furthermore, we will be spending some time on explaining how to upgrade, what to change. Again, for all but the most advanced scenarios this will likely be an exercise of a few find/replaces, deleting some code and doing some straightforward moving. They would be along the lines of:

  • Change bind(bindingContext) to bound() (and use this.scope.bindingContext), and remove your code that updates the values of your bindables (if you don't, that's fine too, its just unnecessary) - we could also add an alias that calls this with bindingContext anyway
  • Remove the 2-3 lines of compositionTransaction related code and use this.$lifecycle.registerTask(new PromiseTask(somePromise)) instead during bind (though we could surely add an adapter for this)

But we'll be looking to reimplement some open source apps in vCurrent with vNext and see how that goes. Then people can take a look and raise any concerns. In the meantime feel free to point out any particular plugins, repos, api's and such that are important to you.

Last but not least..

We are not abandoning vCurrent. Don't panic if you've just invested a lot in vCurrent apps. We will keep trying to find common denominators, improve test coverage, automation, reuse CI functionality, and try to backport new features when possible and realistic if the demand is there.
It is of utmost important to us that the transition will be as smooth as possible and if it isn't, then one still wouldn't have to regret creating an app in vCurrent. I am still building apps in vCurrent and so are other core team members. We all have a direct interest in ensuring things keep working.

All 11 comments

most of the confusion for newcomers seem to be that they're confusing the different lifecycles, not an actual naming issue.
i feel like _attached_ describes what it does really well and would prefer to keep it.
i have no strong opinions on renaming activate.

Rename router hooks activate -> navigateTo

  • Rename [canActivate, activate] to [canNavigateTo, navigateTo]
  • How about canDeactivate and deactivate @Alexander-Taran ?

canNavigateAway & navigateAway ?

I'm all for meaningful names.
attached means something to me.
mounted.. means something to me.

connected is somehow abstract.
what does it mean?
is it created?
connected where?
Actually it means rendered. No?

Thanks for this issue, I love bike shedding (not a joke ;-)). Not on this list but I propose changing created to constructed since that better reflects that it happens after the constructor() is called.

Per @Alexander-Taran comment, if attached means rendered then I like rendered but not sure if that is true or not. Although, the recent blog post describes supporting a render method (for JSX) so that naming similarity might cause confusion. No strong opinion here.

Per @gimerstedt comment, the three lifecycles in vCurrent - one for view-model, one for elements, one for routing - really threw me off and even when I learned the difference it was hard to quickly find documentation of the one I wanted. I am glad to see this distinction mostly going away in vNext.

Is there any way to further combine the router and element lifecycles? Two is much better than three, but I would still expect people to get confused when the calls happen relative to one another. For example, when navigating to a route would activate/navigateTo happen before or after attached/mounted/rendered?.

I am really shooting from the hip here but could you combine the router and element lifecycles into one master lifecycle by having the router call canAttach and attached and just past a special argument to those methods so the developer would know the the router called those methods.

I'll try to summarize:

Broadly speaking there are two main lifecycles from a templating perspective: $bind and $attach.

$bind sets up observation and assigns initial values to everything.

$attach adds the actual nodes/elements to the dom so they become visible.

The reverse is $detach and $unbind.

In addition there is a $hydrate lifecycle which is only called once during initialization of a resource (whereas $bind/$unbind and $attach/$detach can be called many times per instance)

A few days ago I made an initial effort on putting this in a sequence diagram but it's hard to decide which details to leave out and such, because some pieces don't mean much without understanding what effects they have. Anyway : https://www.lucidchart.com/publicSegments/view/54a2c1be-6e40-48a3-898c-a7ff815b4bdc/image.png

$hydrate

  • First lifecycle method after construction
  • Happens during the process of "rendering"
  • Compiles the template, sets up lifecycle hooks, assigns child bindables and attachables to properties
  • Calls render() if that hook is present, which passes the job of rendering to the component instead (for custom rendering)
  • Calls created() at the very end, if that hook is present

$bind

  • Happens directly after $hydrate is done
  • First invokes binding() if the hook is present
  • Then queues bound() (does not invoke it yet) if the hook is present
  • Calls $bind on its children (bindables and/or child resources) -> bindables will then proceed to update targets (which can be DOM nodes or other bindables) and queues connect (which starts observation)
  • At the end, invokes processBoundQueue (only the root initiator will trigger this) indirectly which calls all queued bound()s

$attach

  • Happens directly after $bind is done
  • First invokes attaching() if the hook is present
  • Then invokes $attach on its children
  • Then queues $mount on itself
  • Then queues attached() if the hook is present
  • At the end, invokes processAttachQueue (only the root initiator will trigger this) indirectly which first calls all $mounts (this actually appends the view nodes to the DOM) and then calls all attached() callbacks

There are a couple details I left out here. There's a little more happening to ensure correct state and such, but this is the gist of it. Then there is the reverse which is very similar ($detach and $unbind)

Thanks @fkleuver , that last post clarifies things nicely.

I'm all for cleaning up the lifecycle hooks. I like @Alexander-Taran's suggestion to rename activate to navigateTo or similar. This is the one lifecycle hook that doesn't seem intuitive on first glance.

Having said that, I think most people understand the difference between the hooks after the first day or so, so don't see it as a huge deal.

My preference is that if it is all possible to rename the hooks in a backward compatible way to avoid breaking existing apps/plugins that'd be great. Perhaps by adding a new hook which delegates to the old one or similar. Hooks are a fairly fundamental concept, and changing them will make a lot of the existing resources (blogs etc) out there invalid.

@freshcutdevelopment We've set ourselves up for some degree of backwards compatibility by prepending all internal lifecycle hooks with the dollar symbol, so the normal bind hook is called $bind, which leaves room for adding a backward-compat hook named bind.

The main issue (if you can call it one) with backwards compat, to give you an example:
If you implement bind() in vCurrent, it replaces the internal lifecycle hook and you have to manually update your bindings. in vNext this is not the case: your own hooks do not replace the internal ones and you never need to manually do something that the framework otherwise would (unless you actually want to, of course, such as with render())

As for attached(), a common thing to do is queueMicroTask when your function needs a fully up-to-date component. With vNext we're already guaranteeing this, so queueing won't be necessary.

This is a recurring pattern. Many advanced scenarios in vCurrent require manually tapping into the task queue or other infrastructure pieces, to ensure the timings are correct. This phenomenon is gone in vNext. Migrating from vCurrent to vNext, from a lifecycle perspective, will mostly involve deleting code.

Hooks are a fairly fundamental concept, and changing them will make a lot of the existing resources (blogs etc) out there invalid.

In vNext actually a lot of things have been greatly simplified for end users (the framework itself does more of the heavy lifting so to speak).

So there will be less things you need to learn. It's going to be easier and require less code to do the same stuff. Old resources will become invalid mostly to the extent that they are simply obsolete.
More new advanced scenarios will be possible that weren't possible in vCurrent and those will require fresh resources anyway. We will be doing our best to invest more in API robustness and documentation to make sure people don't have to rely as much on blogs and such anymore.

So with all that said, I think there's an argument to be made for not throwing aliases in there for the sake of keeping old resources valid. Perhaps to ease migration, but that's all.

As an aside note on the upgrade path, you should seriously make it as painless as possible.

If it's basically a rewrite of my enterprise-level application (such as Angular 1 to Angular 2), where's my incentive to do that? Aurelia is struggling for adoption already, so it won't take much to push existing developers somewhere else. I would welcome 1.x names aliased with 'obsolete method' warnings in the console.

I do agree with @swalters , we are in the same situation with a large business app, ported from a flex one, that is just about to be rolled out. So please consider an easy upgrade path as a major requirement !!!

In the end - and perhaps I haven't made this clear enough in my previous comment - nothing fundamentally changes about the framework. @EisenbergEffect also pointed this out in Q2 report I believe. The syntax and conventions stay the same.

Here's the main changes:

  1. Class and method names, API signatures change/move
  2. Various timings change from async to sync
  3. Many internal implementation details that add extra power, capabilities, control and performance, but don't necessarily change what's ultimately rendered by the browser for any given application code base.

Backwards compatibility

We can address 1. with a v1-compat package that adds various aliases and adapters. vNext can be made to expose the same API as vCurrent. This is possible because vNext can do more than vCurrent, not less.

Point 2. is in itself not a problem because many queueMicroTask calls (which will be added to vNext via a compat layer) are simply not necessary anymore. This doesn't mean that this code will break per se. It just won't be the most optimal code.

The true breaking changes are the other way around. Things will be possible in vNext that aren't possible in vCurrent. It would be a one-way upgrade path once you start tapping into those extra capabilities. But that's reasonable, right?

We need your help to identify important APIs and use cases

Obviously, adding a v1-compat package means extra work and testing. It's not realistic to blatantly try and make every nook and cranny of the vCurrent API's work in vNext as well. That would beat the purpose of "vNext".
We need to know which API's (classes, methods, decorators) are most commonly used so we can focus our efforts on making those work (and testing them properly).

Furthermore, we will be spending some time on explaining how to upgrade, what to change. Again, for all but the most advanced scenarios this will likely be an exercise of a few find/replaces, deleting some code and doing some straightforward moving. They would be along the lines of:

  • Change bind(bindingContext) to bound() (and use this.scope.bindingContext), and remove your code that updates the values of your bindables (if you don't, that's fine too, its just unnecessary) - we could also add an alias that calls this with bindingContext anyway
  • Remove the 2-3 lines of compositionTransaction related code and use this.$lifecycle.registerTask(new PromiseTask(somePromise)) instead during bind (though we could surely add an adapter for this)

But we'll be looking to reimplement some open source apps in vCurrent with vNext and see how that goes. Then people can take a look and raise any concerns. In the meantime feel free to point out any particular plugins, repos, api's and such that are important to you.

Last but not least..

We are not abandoning vCurrent. Don't panic if you've just invested a lot in vCurrent apps. We will keep trying to find common denominators, improve test coverage, automation, reuse CI functionality, and try to backport new features when possible and realistic if the demand is there.
It is of utmost important to us that the transition will be as smooth as possible and if it isn't, then one still wouldn't have to regret creating an app in vCurrent. I am still building apps in vCurrent and so are other core team members. We all have a direct interest in ensuring things keep working.

@swalters @pKrav75 Thanks for weighing in here. This is exactly the type of feedback we want and need. Hopefully, the further explanation from @fkleuver helps to address that. Let us know if you need more information or if you have some specific examples you want to talk through.

The most common purpose and use case for activation is to load data from HTTP and cancel navigation if the data isn't available. The biggest problem with the current naming is that the purpose isn't immediately clear. activate suggests "to turn on", but much of the "turning on" behavior is better captured in attached() (think event handlers) or bind().

In practice, this is harder for developers to learn. A lot of time data loading ends up in non-async callbacks like attached(), which then causes issues with bindings. Likewise when developers see activate(), they don't immediately think "load data," especially if they have no intention of canceling. Finally, the canActivate() method is often the best place for activation, as it allows canceling activation, but activate() fits the mental model of loading data better.

load() loaded() unload() unloaded()?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xenoterracide picture xenoterracide  路  12Comments

Vheissu picture Vheissu  路  3Comments

HamedFathi picture HamedFathi  路  13Comments

EisenbergEffect picture EisenbergEffect  路  10Comments

3cp picture 3cp  路  7Comments