Skip to content

Instantly share code, notes, and snippets.

@machty
Last active June 4, 2018 01:00

Revisions

  1. machty revised this gist Nov 22, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -191,7 +191,7 @@ route.

    ## Legacy `LoadingRoute`

    Previous versions of Ember allowed you to define a global `LoadingRoute`
    Previous versions of Ember (somewhat inadvertently) allowed you to define a global `LoadingRoute`
    which would be activated whenever a slow promise was encountered during
    a transition and exited upon completion of the transition. Because the
    `loading` template rendered as a top-level view and not within an
  2. machty revised this gist Nov 22, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -134,7 +134,7 @@ entered, and its templates rendered.
    Loading substates are optional, but if you provide one,
    you are essentially telling Ember that you
    want this async transition to be "eager"; in the absence of destination
    route loading substates, the router will "lazily" remain on the pre-transition routes
    route loading substates, the router will "lazily" remain on the pre-transition route
    while all of the destination routes' promises resolve, and only fully
    transition to the destination route (and renders its templates, etc.)
    once the transition is complete. But once you provide a destination
  3. machty revised this gist Nov 22, 2013. 1 changed file with 0 additions and 30 deletions.
    30 changes: 0 additions & 30 deletions loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -148,36 +148,6 @@ another route fails, a lazy transition will (by default) just remain on the
    previous route, whereas an eager transition will have already left the
    pre-transition route to enter a loading substate.

    ### The `loading` event doesn't bubble above the pivot route

    If you're transitioning from `foo.bar.baz` to `foo.woot.yeah` and a slow
    promise causes a `loading` event to fire, by default, the `loading`
    event will not bubble above `foo` (the "pivot" route), and therefore
    nor would a top-level `LoadingRoute` be entered even if one were
    defined. This is consistent with how shared parent routes are preserved
    when transitioning between two routes, e.g. a transition from
    `foo.bar.baz` to `foo.woot.yeah` doesn't exit and then re-enter `foo`,
    and for the same reason, even if a top-level `loading` route were
    defined, it won't be entered because that would mean exiting `foo`.

    But if you _would_ like to opt into the behavior of entering a
    `loading` route that lives above the pivot route, you can just override
    the `loading` event handler on the pivot route to cause it to bubble:

    ```js
    App.FooRoute = Ember.Route.extend({
    actions: {
    loading: function() {
    return true;
    }
    }
    });
    ```

    Note that on full page reloads, there is no pivot route, so any
    `loading` events in that case are guaranteed to bubble all the way up to
    `ApplicationRoute`.

    ## `error` substates

    Ember provides an analogous approach to `loading` events/substates in
  4. machty revised this gist Oct 13, 2013. 1 changed file with 7 additions and 8 deletions.
    15 changes: 7 additions & 8 deletions loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -256,18 +256,17 @@ transition finishes.

    ### Error Handling / Substates

    [Basic](http://emberjs.jsbin.com/OMeYOna/3/edit)
    [Override `error`, bubble to preserve substate entry](http://emberjs.jsbin.com/OMeYOna/4/edit)
    [Override `error` to transition to login page](http://emberjs.jsbin.com/OMeYOna/5/edit)
    [Multiple nested `error` substates](http://emberjs.jsbin.com/OMeYOna/7/edit)
    - [Basic](http://emberjs.jsbin.com/OMeYOna/3/edit)
    - [Override `error`, bubble to preserve substate entry](http://emberjs.jsbin.com/OMeYOna/4/edit)
    - [Override `error` to transition to login page](http://emberjs.jsbin.com/OMeYOna/5/edit)
    - [Multiple nested `error` substates](http://emberjs.jsbin.com/OMeYOna/7/edit)

    ### Loading Substates

    [Nested loading states](http://emberjs.jsbin.com/OMeYOna/12/edit)
    [Approximate Legacy `LoadingRoute`](http://emberjs.jsbin.com/OMeYOna/13/edit)
    - [Nested loading states](http://emberjs.jsbin.com/OMeYOna/12/edit)
    - [Approximate Legacy `LoadingRoute`](http://emberjs.jsbin.com/OMeYOna/13/edit)

    ### Advanced

    [Nested loading/error states, overriden handlers, bubbling, etc](http://emberjs.jsbin.com/OMeYOna/15/edit)

    - [Nested loading/error states, overriden handlers, bubbling, etc](http://emberjs.jsbin.com/OMeYOna/15/edit)

  5. machty revised this gist Oct 12, 2013. 1 changed file with 21 additions and 0 deletions.
    21 changes: 21 additions & 0 deletions loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -250,3 +250,24 @@ App.ApplicationRoute = Ember.Route.extend({
    This will, like legacy `LoadingRoute`, append a top-level view when the
    router goes into a loading state, and tear down the view once the
    transition finishes.


    ## Examples

    ### Error Handling / Substates

    [Basic](http://emberjs.jsbin.com/OMeYOna/3/edit)
    [Override `error`, bubble to preserve substate entry](http://emberjs.jsbin.com/OMeYOna/4/edit)
    [Override `error` to transition to login page](http://emberjs.jsbin.com/OMeYOna/5/edit)
    [Multiple nested `error` substates](http://emberjs.jsbin.com/OMeYOna/7/edit)

    ### Loading Substates

    [Nested loading states](http://emberjs.jsbin.com/OMeYOna/12/edit)
    [Approximate Legacy `LoadingRoute`](http://emberjs.jsbin.com/OMeYOna/13/edit)

    ### Advanced

    [Nested loading/error states, overriden handlers, bubbling, etc](http://emberjs.jsbin.com/OMeYOna/15/edit)


  6. machty revised this gist Oct 12, 2013. 2 changed files with 252 additions and 157 deletions.
    157 changes: 0 additions & 157 deletions borf.md
    Original file line number Diff line number Diff line change
    @@ -1,157 +0,0 @@
    ## Guide to `loading`/`error` events and substates

    The Ember Router offers some powerful patterns for elegantly handling
    asychronous logic, but one thing that's been missing from the toolkit is
    the concept of `loading` / `error` substates. The closest thing we had
    was a very broken thing called `LoadingRoute`, which, if defined, would
    be "entered" whenever the router went into a loading state (whenever it
    had to resolve a promise in a transition that didn't immediately
    resolve) and be "exited" once the transition completed. It was somewhat
    tacked-on as a `router.js` microlib hangover, and wasn't a robust
    solution as it didn't really embrace proper state machine patterns
    (actions/events fired on `LoadingRoute` weren't actually handleable
    within `LoadingRoute` since `LoadingRoute` was never at any point added
    to the router's hierarchy of active states/route.

    Anyway, things are better now; let's learn about loading and error
    substates.

    ## `loading` substates

    Consider the following:

    ```js
    App.Router.map(function() {
    this.resource('articles', function() {
    this.route('overview');
    });
    });
    ```

    Let's say you navigate to `articles/overview`, and in
    `ArticlesRoute#model`, you return an AJAX query promise to load all of
    the articles that takes 5 seconds or something absurd to complete.
    During this time, your UI isn't really giving you any feedback as to
    what's happening; presently, if you're entering this route after a full page
    refresh, your app will be entirely blank, as you have not actually
    finished fully entering any route and haven't yet displayed any
    templates; if you're navigating to `articles/overview` from another
    route, you'll continue to see the templates from the previous route
    until the articles finish loading, and then, boom, suddenly all the
    templates for `articles/overview` load.

    So, how to configure the behavior?

    ### The `loading` event

    Before going into detail about loading substates, it's important
    to understand the behavior of the `loading` event, upon which all
    of the default behavior regarding loading substates depends.

    The Ember Router allows you to return promises from the various
    `beforeModel`/`model`/`afterModel` hooks in the course of a transition
    (described [here](http://emberjs.com/guides/routing/asynchronous-routing/)).
    These promises pause the transition until they resolve, at which point
    the transition will continue to proceed. If you return a promise from
    one of these hooks, and it doesn't immediately resolve, a `loading`
    event will be fired on that route and bubble upward to
    `ApplicationRoute`. For example:

    ```js
    App.Router.map(function() {
    this.resource('foo', function() {
    this.route('slowModel');
    });
    });

    App.FooSlowModelRoute = Ember.Route.extend({
    model: function() {
    return somePromiseThatTakesAWhileToResolve();
    },
    actions: {
    loading: function(transition, originRoute) {
    // displayLoadingSpinner();

    // Return true to bubble this event to `FooRoute`
    // or `ApplicationRoute`.
    return true;
    }
    }
    });
    ```

    If `FooRoute#model` had returned the slow promise, the `loading`
    event would have fired on `FooRoute` (and not `FooSlowModelRoute`).

    ### The default implementation of the `loading` event

    So already, you've got a nice hook to allow you to configure loading
    behavior in a hierarchical manner. But in addition to this, Ember
    provides a default implementation of the `loading` handler that implements
    the following loading substate behavior we've been alluding to:

    ```js
    App.Router.map(function() {
    this.resource('foo', function() {
    this.resource('bar', function() {
    this.route('baz');
    });
    });
    });
    ```

    If a route with the path `foo.bar.baz` returns a promise that doesn't immediately
    resolve, Ember will try to find a `loading` route in the hierarchy
    above `foo.bar.baz` that it can transition into, starting with
    `foo.bar.baz`'s sibling:

    1. `foo.bar.loading`
    2. `foo.loading`
    3. `loading`

    Ember will find a loading route at the above location if either a) a
    Route subclass has been defined for such a route, e.g.

    1. `App.BarLoadingRoute`
    2. `App.FooLoadingRoute`
    3. `App.LoadingRoute`

    or b) a properly-named loading template has been found, e.g.

    1. `bar/loading`
    2. `foo/loading`
    3. `loading`

    During a slow asynchronous transition, Ember will transition into the
    first loading sub-state/route that it finds, if one exists. The
    intermediate transition into the loading substate happens immediately
    (synchronously), the URL won't be updated, and, unlike other transitions
    that happen while another asynchronous transition is active, the
    currently active async transition won't be aborted.

    After transitioning into a loading substate, the corresponding template
    for that substate, if present, will be rendered into the main outlet of
    the parent route, e.g. `foo.bar.loading`'s template would render into
    `foo.bar`'s outlet. (This isn't particular to loading routes; all
    routes behave this way by default.)

    Once the main async transition into `foo.bar.baz` completes, the loading
    substate will be exited, its template torn down, `foo.bar.baz` will be
    entered, and its templates rendered.

    ### Eager vs. Lazy Async Transitions

    One thing to keep in the back of your mind is that if you provide a
    loading substate on a destination route, you are telling Ember that you
    want this async transition to be "eager"; in the absence of destination
    route loading substates, the router will "lazily" remain on the pre-transition routes
    while all of the destination routes' promises resolve, and only fully
    transition to the destination route (and renders its templates, etc.)
    once the transition is complete. But once you provide a destination
    route loading substate, you are opting into an "eager" transition, which
    is to say that, unlike the "lazy" default, you will eagerly exit the
    source routes (and tear down their templates, etc) in order to
    transition into this substate.



    252 changes: 252 additions & 0 deletions loading-error-substates-guide.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,252 @@
    ## Guide to `loading`/`error` events and substates

    In addition to the techniques described in the
    [Asynchronous Routing Guide](http://emberjs.com/guides/routing/asynchronous-routing/),
    the Ember Router provides powerful yet overridable
    conventions for customizing asynchronous transitions
    between routes by making use of `error` and `loading`
    substates.

    ## `loading` substates

    Consider the following:

    ```js
    App.Router.map(function() {
    this.resource('articles', function() { // -> ArticlesRoute
    this.route('overview'); // -> ArticlesOverviewRoute
    });
    });
    ```

    If you navigate to `articles/overview`, and in `ArticlesRoute#model`,
    you return an AJAX query promise to load all of
    the articles that takes a long time to complete.
    During this time, your UI isn't really giving you any feedback as to
    what's happening; if you're entering this route after a full page
    refresh, your UI will be entirely blank, as you have not actually
    finished fully entering any route and haven't yet displayed any
    templates; if you're navigating to `articles/overview` from another
    route, you'll continue to see the templates from the previous route
    until the articles finish loading, and then, boom, suddenly all the
    templates for `articles/overview` load.

    So, how can we provide some visual feedback during the transition?

    ### The `loading` event

    Before going into detail about loading substates, it's important
    to understand the behavior of the `loading` event.

    The Ember Router allows you to return promises from the various
    `beforeModel`/`model`/`afterModel` hooks in the course of a transition
    (described [here](http://emberjs.com/guides/routing/asynchronous-routing/)).
    These promises pause the transition until they fulfill, at which point
    the transition will resume. If you return a promise from
    one of these hooks, and it doesn't immediately resolve, a `loading`
    event will be fired on that route and bubble upward to
    `ApplicationRoute`. For example:

    ```js
    App.Router.map(function() {
    this.resource('foo', function() { // -> FooRoute
    this.route('slowModel'); // -> FooSlowModelRoute
    });
    });

    App.FooSlowModelRoute = Ember.Route.extend({
    model: function() {
    return somePromiseThatTakesAWhileToResolve();
    },
    actions: {
    loading: function(transition, originRoute) {
    // displayLoadingSpinner();

    // Return true to bubble this event to `FooRoute`
    // or `ApplicationRoute`.
    return true;
    }
    }
    });
    ```

    If `FooRoute#model` had returned the slow promise, the `loading`
    event would have fired on `FooRoute` (and not `FooSlowModelRoute`).

    ### The default implementation of the `loading` event

    So already, you have a hook to allow you to configure loading
    behavior in a hierarchical manner. But in addition to this, Ember
    provides a default implementation of the `loading` handler that implements
    the following loading substate behavior we've been alluding to.

    ```js
    App.Router.map(function() {
    this.resource('foo', function() { // -> FooRoute
    this.resource('bar', function() { // -> BarRoute
    this.route('baz'); // -> BarBazRoute
    });
    });
    });
    ```

    If a route with the path `foo.bar.baz` returns a promise that doesn't immediately
    resolve, Ember will try to find a `loading` route in the hierarchy
    above `foo.bar.baz` that it can transition into, starting with
    `foo.bar.baz`'s sibling:

    1. `foo.bar.loading`
    2. `foo.loading`
    3. `loading`

    Ember will find a loading route at the above location if either a) a
    Route subclass has been defined for such a route, e.g.

    1. `App.BarLoadingRoute`
    2. `App.FooLoadingRoute`
    3. `App.LoadingRoute`

    or b) a properly-named loading template has been found, e.g.

    1. `bar/loading`
    2. `foo/loading`
    3. `loading`

    During a slow asynchronous transition, Ember will transition into the
    first loading sub-state/route that it finds, if one exists. The
    intermediate transition into the loading substate happens immediately
    (synchronously), the URL won't be updated, and, unlike other transitions
    that happen while another asynchronous transition is active, the
    currently active async transition won't be aborted.

    After transitioning into a loading substate, the corresponding template
    for that substate, if present, will be rendered into the main outlet of
    the parent route, e.g. `foo.bar.loading`'s template would render into
    `foo.bar`'s outlet. (This isn't particular to loading routes; all
    routes behave this way by default.)

    Once the main async transition into `foo.bar.baz` completes, the loading
    substate will be exited, its template torn down, `foo.bar.baz` will be
    entered, and its templates rendered.

    ### Eager vs. Lazy Async Transitions

    Loading substates are optional, but if you provide one,
    you are essentially telling Ember that you
    want this async transition to be "eager"; in the absence of destination
    route loading substates, the router will "lazily" remain on the pre-transition routes
    while all of the destination routes' promises resolve, and only fully
    transition to the destination route (and renders its templates, etc.)
    once the transition is complete. But once you provide a destination
    route loading substate, you are opting into an "eager" transition, which
    is to say that, unlike the "lazy" default, you will eagerly exit the
    source routes (and tear down their templates, etc) in order to
    transition into this substate.

    This has implications on error handling, i.e. when a transition into
    another route fails, a lazy transition will (by default) just remain on the
    previous route, whereas an eager transition will have already left the
    pre-transition route to enter a loading substate.

    ### The `loading` event doesn't bubble above the pivot route

    If you're transitioning from `foo.bar.baz` to `foo.woot.yeah` and a slow
    promise causes a `loading` event to fire, by default, the `loading`
    event will not bubble above `foo` (the "pivot" route), and therefore
    nor would a top-level `LoadingRoute` be entered even if one were
    defined. This is consistent with how shared parent routes are preserved
    when transitioning between two routes, e.g. a transition from
    `foo.bar.baz` to `foo.woot.yeah` doesn't exit and then re-enter `foo`,
    and for the same reason, even if a top-level `loading` route were
    defined, it won't be entered because that would mean exiting `foo`.

    But if you _would_ like to opt into the behavior of entering a
    `loading` route that lives above the pivot route, you can just override
    the `loading` event handler on the pivot route to cause it to bubble:

    ```js
    App.FooRoute = Ember.Route.extend({
    actions: {
    loading: function() {
    return true;
    }
    }
    });
    ```

    Note that on full page reloads, there is no pivot route, so any
    `loading` events in that case are guaranteed to bubble all the way up to
    `ApplicationRoute`.

    ## `error` substates

    Ember provides an analogous approach to `loading` events/substates in
    the case of errors encountered during a transition.

    ```js
    App.Router.map(function() {
    this.resource('articles', function() { // -> ArticlesRoute
    this.route('overview'); // -> ArticlesOverviewRoute
    });
    });
    ```

    If `ArticlesOverviewRoute#model` returns a promise that rejects (because, for
    instance, the server returned an error, or the user isn't logged in,
    etc.), an `error` event will fire on `ArticlesOverviewRoute` and bubble upward.
    This `error` event can be handled and used to display an error message,
    redirect to a login page, etc., but similar to how the default `loading`
    event handlers are implemented, the default `error` handlers
    will look for an appropriate error substate to
    enter, if one can be found.

    For instance, an error thrown or rejecting promise returned from
    `ArticlesOverviewRoute#model` (or `beforeModel` or `afterModel`)
    will look for:

    1. Either `ArticlesErrorRoute` or `articles/error` template
    2. Either `ErrorRoute` or `error` template

    If one of the above is found, the router will immediately transition into
    that substate (without updating the URL). The "reason" for the error
    (i.e. the exception thrown or the promise reject value) will be passed
    to that error state as its `model`.

    If no viable error substates can be found, an error message will be
    logged.

    The only way in which `loading`/`error` substate resolution differs is
    that `error` events will continue to bubble above a transition's pivot
    route.

    ## Legacy `LoadingRoute`

    Previous versions of Ember allowed you to define a global `LoadingRoute`
    which would be activated whenever a slow promise was encountered during
    a transition and exited upon completion of the transition. Because the
    `loading` template rendered as a top-level view and not within an
    outlet, it could be used for little more than displaying a loading
    spinner during slow transitions. Loading events/substates give you far
    more control, but if you'd like to emulate something similar to legacy
    `LoadingRoute` behavior, you could do as follows:

    ```js
    App.ApplicationRoute = Ember.Route.extend({
    actions: {
    loading: function() {
    var view = Ember.View.create({
    templateName: 'global-loading',
    elementId: 'global-loading'
    }).append();

    this.router.one('didTransition', function() {
    view.destroy();
    });
    }
    }
    });
    ```

    This will, like legacy `LoadingRoute`, append a top-level view when the
    router goes into a loading state, and tear down the view once the
    transition finishes.
  7. machty created this gist Oct 12, 2013.
    157 changes: 157 additions & 0 deletions borf.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,157 @@
    ## Guide to `loading`/`error` events and substates

    The Ember Router offers some powerful patterns for elegantly handling
    asychronous logic, but one thing that's been missing from the toolkit is
    the concept of `loading` / `error` substates. The closest thing we had
    was a very broken thing called `LoadingRoute`, which, if defined, would
    be "entered" whenever the router went into a loading state (whenever it
    had to resolve a promise in a transition that didn't immediately
    resolve) and be "exited" once the transition completed. It was somewhat
    tacked-on as a `router.js` microlib hangover, and wasn't a robust
    solution as it didn't really embrace proper state machine patterns
    (actions/events fired on `LoadingRoute` weren't actually handleable
    within `LoadingRoute` since `LoadingRoute` was never at any point added
    to the router's hierarchy of active states/route.

    Anyway, things are better now; let's learn about loading and error
    substates.

    ## `loading` substates

    Consider the following:

    ```js
    App.Router.map(function() {
    this.resource('articles', function() {
    this.route('overview');
    });
    });
    ```

    Let's say you navigate to `articles/overview`, and in
    `ArticlesRoute#model`, you return an AJAX query promise to load all of
    the articles that takes 5 seconds or something absurd to complete.
    During this time, your UI isn't really giving you any feedback as to
    what's happening; presently, if you're entering this route after a full page
    refresh, your app will be entirely blank, as you have not actually
    finished fully entering any route and haven't yet displayed any
    templates; if you're navigating to `articles/overview` from another
    route, you'll continue to see the templates from the previous route
    until the articles finish loading, and then, boom, suddenly all the
    templates for `articles/overview` load.

    So, how to configure the behavior?

    ### The `loading` event

    Before going into detail about loading substates, it's important
    to understand the behavior of the `loading` event, upon which all
    of the default behavior regarding loading substates depends.

    The Ember Router allows you to return promises from the various
    `beforeModel`/`model`/`afterModel` hooks in the course of a transition
    (described [here](http://emberjs.com/guides/routing/asynchronous-routing/)).
    These promises pause the transition until they resolve, at which point
    the transition will continue to proceed. If you return a promise from
    one of these hooks, and it doesn't immediately resolve, a `loading`
    event will be fired on that route and bubble upward to
    `ApplicationRoute`. For example:

    ```js
    App.Router.map(function() {
    this.resource('foo', function() {
    this.route('slowModel');
    });
    });

    App.FooSlowModelRoute = Ember.Route.extend({
    model: function() {
    return somePromiseThatTakesAWhileToResolve();
    },
    actions: {
    loading: function(transition, originRoute) {
    // displayLoadingSpinner();

    // Return true to bubble this event to `FooRoute`
    // or `ApplicationRoute`.
    return true;
    }
    }
    });
    ```

    If `FooRoute#model` had returned the slow promise, the `loading`
    event would have fired on `FooRoute` (and not `FooSlowModelRoute`).

    ### The default implementation of the `loading` event

    So already, you've got a nice hook to allow you to configure loading
    behavior in a hierarchical manner. But in addition to this, Ember
    provides a default implementation of the `loading` handler that implements
    the following loading substate behavior we've been alluding to:

    ```js
    App.Router.map(function() {
    this.resource('foo', function() {
    this.resource('bar', function() {
    this.route('baz');
    });
    });
    });
    ```

    If a route with the path `foo.bar.baz` returns a promise that doesn't immediately
    resolve, Ember will try to find a `loading` route in the hierarchy
    above `foo.bar.baz` that it can transition into, starting with
    `foo.bar.baz`'s sibling:

    1. `foo.bar.loading`
    2. `foo.loading`
    3. `loading`

    Ember will find a loading route at the above location if either a) a
    Route subclass has been defined for such a route, e.g.

    1. `App.BarLoadingRoute`
    2. `App.FooLoadingRoute`
    3. `App.LoadingRoute`

    or b) a properly-named loading template has been found, e.g.

    1. `bar/loading`
    2. `foo/loading`
    3. `loading`

    During a slow asynchronous transition, Ember will transition into the
    first loading sub-state/route that it finds, if one exists. The
    intermediate transition into the loading substate happens immediately
    (synchronously), the URL won't be updated, and, unlike other transitions
    that happen while another asynchronous transition is active, the
    currently active async transition won't be aborted.

    After transitioning into a loading substate, the corresponding template
    for that substate, if present, will be rendered into the main outlet of
    the parent route, e.g. `foo.bar.loading`'s template would render into
    `foo.bar`'s outlet. (This isn't particular to loading routes; all
    routes behave this way by default.)

    Once the main async transition into `foo.bar.baz` completes, the loading
    substate will be exited, its template torn down, `foo.bar.baz` will be
    entered, and its templates rendered.

    ### Eager vs. Lazy Async Transitions

    One thing to keep in the back of your mind is that if you provide a
    loading substate on a destination route, you are telling Ember that you
    want this async transition to be "eager"; in the absence of destination
    route loading substates, the router will "lazily" remain on the pre-transition routes
    while all of the destination routes' promises resolve, and only fully
    transition to the destination route (and renders its templates, etc.)
    once the transition is complete. But once you provide a destination
    route loading substate, you are opting into an "eager" transition, which
    is to say that, unlike the "lazy" default, you will eagerly exit the
    source routes (and tear down their templates, etc) in order to
    transition into this substate.