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

Prevent unnecessary re-rendering when only route context has changed #2563

Merged
merged 2 commits into from Apr 26, 2013

Conversation

ef4
Copy link
Contributor

@ef4 ef4 commented Apr 26, 2013

It should only be necessary to run renderTemplate when a route is
entered. Subsequent changes to a route's context Just Work(tm) thanks
to the usual magic of bindings.

In addition to a theoretical performance benefit, this change gives
the controller more control over precisely when and how the view will
transition from model-to-model. (In my case, I'm using this capability
to nicely animate the transition.)

This change doesn't break any unit tests or change the API. But if
someone is doing something weird in renderTemplate (like choosing
their template based on the model, or doing something else with
side-effects), they will get breakage. I think those cases are
sufficiently pathological not to worry about.

@stefanpenner
Copy link
Member

@ef4 can you add a regression test.

@ef4
Copy link
Contributor Author

ef4 commented Apr 26, 2013

Sure.

@stefanpenner
Copy link
Member

seems to be failing.


Ember.run(function() {
router.handleURL("/page/second");
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a ; here and on line 1798 and the build should pass

@robharper
Copy link
Contributor

👍 This would be extremely helpful. Would provide a solution to my StackOverflow question Avoid current route re-enter (and re-render) on url dynamic segment update

@ef4
Copy link
Contributor Author

ef4 commented Apr 26, 2013

Freaking semicolons... Sorry, I'm spoiled by mostly writing CoffeeScript, and I could swear I had run the whole test suite.

Should be fixed now. I squashed the minor edits.

@stefanpenner
Copy link
Member

understood bro.

FYI our ci runs rake 'test[standard]' that may catch some things missed in the quicker rake test

seems to no longer merge cleanly. (been a busy ember day)

ef4 added 2 commits April 26, 2013 14:37
It should only be necessary to run `renderTemplate` when a route is
entered. Subsequent changes to a route's context Just Work(tm) thanks
to the usual magic of bindings.

In addition to a theoretical performance benefit, this change gives
the controller more control over precisely when and how the view will
transition from model-to-model. (In my case, I'm using this capability
to nicely animate the transition.)

This change doesn't break any unit tests or change the API. But if
someone is doing something weird in `renderTemplate` (like choosing
their template based on the model, or doing something else with
side-effects), they will get breakage. I think those cases are
sufficiently pathological not to worry about.
@ef4
Copy link
Contributor Author

ef4 commented Apr 26, 2013

Rebased and resolved conflicts.

stefanpenner added a commit that referenced this pull request Apr 26, 2013
Prevent unnecessary re-rendering when only route context has changed
@stefanpenner stefanpenner merged commit 082a987 into emberjs:master Apr 26, 2013
@stefanpenner
Copy link
Member

@ef4 thanks dude!

@robharper
Copy link
Contributor

Made my friday, thanks!

@stefanpenner
Copy link
Member

@robharper happy friday!

@machty
Copy link
Contributor

machty commented Apr 27, 2013

@ef4 could you make the proper PR to https://github.com/tildeio/router.js for the change you did to vendor/router.js? We need to keep that repo in sync

@stefanpenner
Copy link
Member

@machty oh shit, good catch.... Can you do this if @ef4 doesn't have a chance?

@machty
Copy link
Contributor

machty commented Apr 28, 2013

@stefanpenner Sure, no problem, but I want to reconsider this PR (sorry I didn't see it earlier). I don't think router.js should know anything about renderTemplates. @ef4 is there some reason this logic can't all be in Ember.Router?

@ef4
Copy link
Contributor Author

ef4 commented Apr 28, 2013

@machty It is possible to solve this issue without changing router.js, but I think that solution is more fragile.

Currently, the router knows about three hooks on each route: enter, setup, and exit.

We want the route to render its template only when we're entering for the first time, but after setup has run. None of the three existing hooks does that by itself.

Now, it's possible to make the route more stateful instead: it can set a flag when enter is called, and then check & clear the flag in setup. But this is fragile. It's easy to end up with incorrect state after somebody throws an exception, or redirects, etc, etc.

The router is the part of the code that reliably knows whether we're just updating context or entering. It should be responsible for firing the right hook that conveys that information directly.

My change creates a fourth hook and calls it setupTemplate. If that name is too purpose-specific, maybe there's something more generic to call it.

But there are even better ways to redesign the API. If I was doing it from scratch:
* enter would be called when entering, and it would receive the context, eliminating the need for a separate setup call.
* update would be called when context is changed on an existing route
* exit would be the same

Given these, each route change would fire only one hook, making the intent very clear to the receiving route. The route is still free to implement enter and update with shared code for installing the context -- but that's an internal implementation detail of the route, it doesn't belong in the API.

@ef4
Copy link
Contributor Author

ef4 commented Apr 28, 2013

As it stands, the existing API is not good enough, because setup means both "setup" and "just update the context".

@machty
Copy link
Contributor

machty commented Apr 28, 2013

@ef4 we're doing some substantial router refactoring that will address many of these issues you've brought up. I'll definitely keep you posted on this stuff as they come out.

But in the meantime we want to keep router.js separate from any knowledge of rendering templates and so on, even if a more Ember-based approach seems more fragile. I think something in Ember.Route that checks for a duplicate render would make sense and I think we'll be moving this logic there. Would you want to take a stab at that?

@stefanpenner lemme know if you agree

@ef4
Copy link
Contributor Author

ef4 commented Apr 28, 2013

"But in the meantime we want to keep router.js separate from any knowledge of rendering templates and so on"

But it is separate, and there's still no knowledge of template rendering in router.js. All we're really doing here is creating a new hook, and now you're really making me wish I named it something different, because the name is confusing the issue. Call it "finishedEntering" instead.

"In the meantime", multiple people are running up against this issue, and we have a working solution that does not complicate the router or make it any harder to refactor.

@machty
Copy link
Contributor

machty commented Apr 28, 2013

@ef4 let's keep this PR in the meantime. I agree with you, I just want things to be in the right place.

Taking a look at the code now and I there's a more sensible solution with setContext and contextDidChange defined in Ember.Route. Did you investigate this path? Was there some reason this wouldn't work?

@ef4
Copy link
Contributor Author

ef4 commented Apr 28, 2013

I'm not sure I understand what you mean.

contextDidChange fires for both context-only changes and route changes, and it fires before setup has a chance to run.

@machty
Copy link
Contributor

machty commented Apr 28, 2013

Sorry, @ef4, I'm making the mistake I've chided others for in that I'm putting in the minimal effort to point out something is wrong without taking the proper time to fully understand the problem to provide a better solution; I'll try to be more on the ball from now on.

What if we just made the render function idempotent, and not tear down a view if there's an identical one already rendered with the same controller?

@ef4
Copy link
Contributor Author

ef4 commented Apr 29, 2013

Making render idempotent could work. I will investigate.
On Apr 28, 2013 3:14 PM, "Alex Matchneer" notifications@github.com wrote:

Sorry, @ef4 https://github.com/ef4, I'm making the mistake I've chided
others for in that I'm putting in the minimal effort to point out something
is wrong without taking the proper time to fully understand the problem to
provide a better solution; I'll try to be more on the ball from now on.

What if we just made the render function idempotent, and not tear down a
view if there's an identical one already rendered with the same controller?


Reply to this email directly or view it on GitHubhttps://github.com//pull/2563#issuecomment-17140580
.

@machty
Copy link
Contributor

machty commented Apr 29, 2013

@ef4, thanks. For what it's worth, there's been some discussion about idempotent rendering as a possible way to facilitate state-based animations, e.g. if you have routes A and B, and you'd like some UI element in B to slide in (but you don't want to render it in A), you could feasibly have both B and transition state AtoB render B's UI element with the same args without it rendering the view again when it actually gets to B. Then if you just navigated to B's route in a separate browser tab, it would go straight to route B without the animation and render correctly. So this is another reason I'm eager to try out the idempotent renders approach.

@ef4
Copy link
Contributor Author

ef4 commented Apr 29, 2013

Yeah, animation is the main reason I opened this pull request in the first place. As written, it allows you to animate context changes within the same route, and I'd be happy to see a solution for cross-route animation as well.

But idempotent render doesn't fully solve the problem. We would also need to make exit stop tearing down views automatically. Right now the view is destroyed long before render has a chance to notice that it's the same.

stefanpenner added a commit to stefanpenner/ember.js that referenced this pull request Apr 29, 2013
ef4 added a commit to ef4/ember.js that referenced this pull request Apr 29, 2013
This change prevents us from tearing down and recreating a view when
the new view is functionally identical to the old view.

This has important benefits:

 - when you're changing context within a route, the view itself
   remains stable, and the controller has full control over how and
   when to update the data it's presenting to the view
   layer. (Previously, you got a forced re-render immediately whether
   you were ready for it or not.)

 - when you're changing between routes, you can decide to keep the
   prior view in place, and replace it later when you're ready. (Think
   animated transitions between routes.)

This supercedes the proposed changes in PR emberjs#2563 (which has already
been reverted).
@ef4 ef4 mentioned this pull request Apr 29, 2013
marcioj pushed a commit to marcioj/ember.js that referenced this pull request May 8, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants