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

Swap timing #8

Closed
jods4 opened this issue Aug 18, 2015 · 14 comments
Closed

Swap timing #8

jods4 opened this issue Aug 18, 2015 · 14 comments

Comments

@jods4
Copy link
Contributor

jods4 commented Aug 18, 2015

I think the way the swap is done in <router-view> is not ideal for perceived speed and loaders / animation purposes.

The main steps of nav. lifecycle are (in order):

  1. canDeactivate(), which can veto
  2. canActivate(), which can veto
  3. deactivate(), no turning back from here
  4. activate().
  5. swap the views.

activate() is a great place to load data for the next screen. It ensures that all required data is there before the screen is actually bound (better for perf, no flickering on screen, etc.).
Because it is allowed to return a Promise, this is well handled by the Router, which will wait before displaying the new screen. Moreover you can use isNavigating to display a loader indicator on screen.

What doesn't fit nicely in my opinion is the way the views are swapped. Basically, all steps 1 to 4 are first done and then the old view is removed and the new view put in place as one operation. This makes a slightly clumsy experience where you must put some kind of modal blocker on top of the old screen (or fake "remove" it yourself) while waiting for activate() to resolve (e.g. for data to be loaded from the web server).

I think it would be a better experience if it went like this:

  1. canDeactivate(), which can veto
  2. canActivate(), which can veto
  3. deactivate(), no turning back from here
  4. Remove the old view (directly or with animation. Note: if animated, don't block on completion yet and move on with the next step.)
  5. activate().
  6. Insert the new view, after 4. (possible animation) and 5. (possible Promise) both completed.

This way, the old UI naturally goes away, you can display a "loading" indicator easily and the new view appears when it's ready. Better.

Moreover there's an important benefit in terms of perceived speed. If you use an animation to remove your old view at step 4, it will hide (some of) the latency of your loading at step 5 (provided you don't wait for animation completion before step 6, so that 4 & 5 happen in parallel).

This is an important issue because there is no good way to coordinate all this without proper Aurelia support.

@EisenbergEffect
Copy link
Contributor

I like the sound of this. @bryanrsmith Please read above and lets start a discussion about this. I'd like to know what you think.

@EisenbergEffect
Copy link
Contributor

I think if we ever added this, we would need to make it configurable because certain animations will require this to be handled differently.

@EisenbergEffect
Copy link
Contributor

Closing this since we aren't changing the internal pipeline at this point. There is a new swap order property on the router-view which allows controlling animation timing.

@jods4
Copy link
Contributor Author

jods4 commented Jul 4, 2016

Can you provide details about the swap order property? Does it support the scenario described in this issue?

@joelcoxokc
Copy link
Contributor

joelcoxokc commented Jul 8, 2016

@jods4 Swap order simply allows you specify the order the next view and last view get created and removed.

for example, specifying swap-order="with" allows the views to be created and destroyed in parallel.

Something that may be of interest to you, is a new method I added to the ViewSlot.
animateView

This method takes a view, and a direction, whether enter or leave
This will be called before a view enters and before a view leaves. and returns a promise when the animation completes. However, unless you know exactly how the viewSlot works, I highly recommend not replacing this method, because it could cause some serious issues going down the line.

Something you could do is, replace it with a method that re-calls the original animateView and publishes an event in the event-aggregator. However, make sure you return the promises from the original animateView.

@jods4
Copy link
Contributor Author

jods4 commented Jul 8, 2016

@joelcoxokc I had a look. This allows all kinds of transitions (e.g. cross-fade, one after the other, etc.). It's nice.

But it doesn't allow what I wanted here, which is being able to animate out the current view before activate is called, which hides the latency of the activate when it loads data.

@EisenbergEffect Even if it's not supported by Aurelia proper, it would be possible to do this as an outside extension if you would just add a pipeline hook between DeactivatePreviousStep and ActivateNextStep. This is rather trivial and would enable the following craziness as an extension:

  • I register a postdeactivate step that does the following:
  • It reaches through the navigationInstruction to the viewPorts, pretty much like CommitChanges does.
  • It animates/removes the old views from their slots. The promise for that is stashed aside in the instruction but not returned.
  • I also register a prerender step that grab that promise and returns it now. This is for good timing: activate and the out animation run in parallel; and both need to complete before the next view comes in.
  • The commit step does its thing, which at this point is simply bringing in the new view.

So I think I can do what I wanted, if you would just add a pipeline step between activate and deactivate.

@EisenbergEffect
Copy link
Contributor

See here: https://github.com/aurelia/router/blob/master/src/pipeline-provider.js#L36
Feel free to submit a PR to add additional slots. I'd be fine with that.

(Note that you can always create your own pipeline provider, register it with the DI and then have full control.)

@jods4
Copy link
Contributor Author

jods4 commented Jul 8, 2016

(Note that you can always create your own pipeline provider, register it with the DI and then have full control.)

Right, I forgot that. So yes, I could hack this without a single change to Aurelia. Let me know what you think: do you want to add a new pipeline step or rather not?

@EisenbergEffect
Copy link
Contributor

Send a PR with what you have in mind and let us look at it. It's fine with me, based on your description.

@jods4
Copy link
Contributor Author

jods4 commented Jul 8, 2016

Sorry, forgot to ask the tricky question: what name would be a good name? preActivate is unfortunate in the current pipeline 😢

@EisenbergEffect
Copy link
Contributor

I'll let you figure that out 😆

@joelcoxokc
Copy link
Contributor

I see. @jods4 I just implemented something similar for the next hub release... to allow the side panel to animate before the view loads. A PR would be great, that we can make it standard

@jods4
Copy link
Contributor Author

jods4 commented Jul 10, 2016

@EisenbergEffect @joelcoxokc
Look at this and let me know what you think: https://github.com/jods4/aurelia-animate-early

@EisenbergEffect
Copy link
Contributor

Cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants