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

RouteReuseStrategy.shouldReuseRoute has arguments swapped #16192

Closed
poke opened this issue Apr 20, 2017 · 18 comments
Closed

RouteReuseStrategy.shouldReuseRoute has arguments swapped #16192

poke opened this issue Apr 20, 2017 · 18 comments
Labels
area: router freq2: medium help wanted An issue that is suitable for a community contributor (based on its complexity/scope). router: RouteReuseStrategy state: confirmed type: bug/fix
Milestone

Comments

@poke
Copy link

poke commented Apr 20, 2017

Hey, I noticed the following inconsistency regarding the arguments passed to RouteReuseStrategy.shouldReuseRoute in create_router_state.ts:

In line 26, the function is called like this:

routeReuseStrategy.shouldReuseRoute(curr.value, prevState.value.snapshot)

So the first argument is the current state, and the second argument is the one before. This matches the documentation.

However, a few lines below, the following call is made:

const children = createOrReuseChildren(routeReuseStrategy, curr, prevState);

So here, curr and prevState are passed to createOrReuseChildren which in turn does the following:

function createOrReuseChildren(
    routeReuseStrategy: RouteReuseStrategy, curr: TreeNode<ActivatedRouteSnapshot>,
    prevState: TreeNode<ActivatedRoute>) {
  return curr.children.map(child => {
    for (const p of prevState.children) {
      if (routeReuseStrategy.shouldReuseRoute(p.value.snapshot, child.value)) {
        return createNode(routeReuseStrategy, child, p);
      }
    }
    return createNode(routeReuseStrategy, child);
  });
}

Here are two iterations happening, once of the children of curr and once for the children of prevState. Within the inner iteration, child is the child of curr, and p is the child of prevState. However, the call to shouldReuseRoute now is in reverse to the one before:

routeReuseStrategy.shouldReuseRoute(p.value.snapshot, child.value)

So first, the previous state child is passed and then the current state child, which is the reversed order to the function definition, where the second argument is the older state.

Now I’m not perfectly sure if this isn’t the desired behavior, since I don’t completely understand how the reuse strategy works, but this appears very odd to me. And it would explain the odd behavior I see when trying to implement a reuse strategy that dynamically decides based on the route. Right now, when switching from route A to B, I get two calls to shouldReuseRoute, once with A => B and once B => A making it impossible to create a strategy handle only one direction.

If someone confirms that this is a bug, I can gladly provide a pull request for the fix.

@geetee24
Copy link

I concur with poke. it is not yet fixed at least in v 4.1.1

@compeek
Copy link

compeek commented Sep 15, 2017

I just noticed this on 4.3.6 as well and then found this issue to confirm I'm not the only one.

@jasonaden jasonaden added severity3: broken type: bug/fix area: router freq2: medium help wanted An issue that is suitable for a community contributor (based on its complexity/scope). and removed area: router labels Dec 13, 2017
@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@arlowhite
Copy link

Still not fixed in 5.2.3
The two arguments at the call site just need to be switched right?
@vsavkin Could you double-check this line?

if (routeReuseStrategy.shouldReuseRoute(p.value.snapshot, child.value)) {

@sliekens
Copy link
Contributor

sliekens commented Feb 7, 2018

I can confirm this is wrong. I'm trying to implement a route reuse strategy for a named router outlet and I'm getting swapped curr and future arguments for the named outlet.

Right now I'm doing something like this to work around the issue.

shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    if (future.outlet !== 'primary') {
        // do the switcheroo
        [future, curr] = [curr, future];
    }
    if (curr.routeConfig && curr.routeConfig.data && curr.routeConfig.data.useOnce) {
        return false;
    } else {
        return future.routeConfig === curr.routeConfig;
    }
}

@lemoinem
Copy link
Contributor

I have a trivial fix, but I'm not sure what tests need to be added so the PR is acceptable:
lemoinem@deaeb3e

I want to note that inverting the two arguments is not enough:
createOrReuseChildren invokes the method in the wrong order and then calls createNode which invokes the method again, but with the arguments in the correct order this time.

Basically, any asymmetric routeReuseStrategy is impossible because of this issue.
How come this hasn't been fixed in more than a year and a half?

@sliekens
Copy link
Contributor

sliekens commented Oct 3, 2018

@lemoinem my workaround for that is to swap the arguments only if the first snapshot targets the 'primary' outlet. This guarantees that the call did not originate from createOrReuseChildren.

Of course that workaround will break when createOrReuseChildren gets fixed.

@lemoinem
Copy link
Contributor

@StevenLiekens This doesn't work for me.
I am looking at routes in the primary outlets. And the methods is invoked once with the arguments in the correct order and once with the arguments in the wrong order. With the exact same arguments.

@sliekens
Copy link
Contributor

Weird I thought only child routes were affected by the swapped arguments bug

@liutian
Copy link

liutian commented Mar 15, 2019

Still not fixed in 7.2.7

@gabriele-fanchini
Copy link

versione 7.2.10 still effected. It would be great to have some news about it...

@liutian
Copy link

liutian commented Aug 28, 2019

Still not fixed in 8.2.3

@lemoinem
Copy link
Contributor

There is a one-line fix PR waiting to be reviewed/merged, but I had no feedback. I'm open to any suggestion as to what to do to get it merged or attract some kind of attention.

@skaindl
Copy link

skaindl commented Feb 4, 2020

I just ran into this as well. I find it rather sad that this bug is open so long, when someone just has to change a single line to fix a major inconsistency.

@jasonaden @mhevery, could someone please have a look at the PR for this?

@samlanzz
Copy link

samlanzz commented Feb 18, 2020

Any news on this? I seem to be losing my route.children along the way due to this problem.

@atscott atscott added state: confirmed triage #1 needs reproduction This issue needs a reproduction in order for the team to investigate further and removed state: confirmed labels May 26, 2020
@atscott
Copy link
Contributor

atscott commented May 26, 2020

Hi all,

I agree that the code looks suspect and is likely incorrect. That said, it's difficult to justify changing anything without a proper demonstration of incorrect behavior. Can someone create a minimal repro on StackBlitz that demonstrates unexpected behavior due to this bug?

@johnny-mh
Copy link

@atscott
https://stackblitz.com/edit/angular-ivy-crrs-swapped
You can check the future and curr swapped by clicking settings detail link.
shouldReuseRoute invoke twice. 1st is swapped future and curr ambiguously.

@atscott atscott removed the needs reproduction This issue needs a reproduction in order for the team to investigate further label Jun 17, 2020
atscott pushed a commit to lemoinem/angular that referenced this issue Jul 7, 2020
The `createOrReuseChildren` function calls shouldReuseRoute with the
previous child values use as the future and the future child value used
as the current argument. This is incosistent with the argument order in
`createNode`. This inconsistent order can make it difficult/impossible
to correctly implement the `shouldReuseRoute` function. Usually this
order doesn't matter because simple equality checks are made on the
args and it doesn't matter which is which.

More detail can be found in the bug report: angular#16192.

Fix angular#16192
atscott pushed a commit to lemoinem/angular that referenced this issue Jul 7, 2020
The `createOrReuseChildren` function calls shouldReuseRoute with the
previous child values use as the future and the future child value used
as the current argument. This is incosistent with the argument order in
`createNode`. This inconsistent order can make it difficult/impossible
to correctly implement the `shouldReuseRoute` function. Usually this
order doesn't matter because simple equality checks are made on the
args and it doesn't matter which is which.

More detail can be found in the bug report: angular#16192.

Fix angular#16192
atscott pushed a commit to lemoinem/angular that referenced this issue Jul 7, 2020
The `createOrReuseChildren` function calls shouldReuseRoute with the
previous child values use as the future and the future child value used
as the current argument. This is incosistent with the argument order in
`createNode`. This inconsistent order can make it difficult/impossible
to correctly implement the `shouldReuseRoute` function. Usually this
order doesn't matter because simple equality checks are made on the
args and it doesn't matter which is which.

More detail can be found in the bug report: angular#16192.

Fix angular#16192
atscott pushed a commit to lemoinem/angular that referenced this issue Jul 8, 2020
The `createOrReuseChildren` function calls shouldReuseRoute with the
previous child values use as the future and the future child value used
as the current argument. This is incosistent with the argument order in
`createNode`. This inconsistent order can make it difficult/impossible
to correctly implement the `shouldReuseRoute` function. Usually this
order doesn't matter because simple equality checks are made on the
args and it doesn't matter which is which.

More detail can be found in the bug report: angular#16192.

Fix angular#16192

BREAKING CHANGE: This change corrects the argument order when calling
RouteReuseStrategy#shouldReuseRoute. Previously, when evaluating child
routes, they would be called with the future and current arguments would
be swapped. If your RouteReuseStrategy relies specifically on only the future
or current snapshot state, you may need to update the shouldReuseRoute
implementation's use of "future" and "current" ActivateRouteSnapshots.
josephperrott pushed a commit to josephperrott/angular that referenced this issue Sep 15, 2020
…r#26949)

The `createOrReuseChildren` function calls shouldReuseRoute with the
previous child values use as the future and the future child value used
as the current argument. This is incosistent with the argument order in
`createNode`. This inconsistent order can make it difficult/impossible
to correctly implement the `shouldReuseRoute` function. Usually this
order doesn't matter because simple equality checks are made on the
args and it doesn't matter which is which.

More detail can be found in the bug report: angular#16192.

Fix angular#16192

BREAKING CHANGE: This change corrects the argument order when calling
RouteReuseStrategy#shouldReuseRoute. Previously, when evaluating child
routes, they would be called with the future and current arguments would
be swapped. If your RouteReuseStrategy relies specifically on only the future
or current snapshot state, you may need to update the shouldReuseRoute
implementation's use of "future" and "current" ActivateRouteSnapshots.

PR Close angular#26949
@Pupskuchen
Copy link

I know this has been fixed, but since that fix hasn't been released yet I had to come up with a workaround to get a consistent order of arguments. Now I haven't used this for a long time, but so far it seems like it's doing its job pretty … consistently?

So I thought I'd share it with you, maybe it'll be helpful for someone or you have a different idea. In the leaderboard of worst code I've written, this is ranked pretty high:

private createNodeStack: string;
public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    const caller = new Error().stack.split('\n')[1];

    /*
     * See comment below for why we do this.
     * When called for the first time, store caller from stack trace.
     * The first call will be from 'createNode'.
     */
    if (!this.createNodeStack) {
        this.createNodeStack = caller;
    }

    /*
     * Workaround for Angular bug #16192 (https://github.com/angular/angular/issues/16192)
     * Argument order for calls of this function is currently not consistent so
     * we need to swap them if called from 'createOrReuseChildren'.
     * This will break in a future release when the order is fixed.
     */
    if (caller !== this.createNodeStack) {
        [future, curr] = [curr, future];
    }

    [] // do your thing here
}

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Nov 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: router freq2: medium help wanted An issue that is suitable for a community contributor (based on its complexity/scope). router: RouteReuseStrategy state: confirmed type: bug/fix
Projects
None yet
Development

Successfully merging a pull request may close this issue.