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

Navigation events are called twice when using HashLocationStrategy #16710

Closed
TetianaP opened this issue May 10, 2017 · 35 comments · Fixed by bitwarden/clients#1935
Closed

Navigation events are called twice when using HashLocationStrategy #16710

TetianaP opened this issue May 10, 2017 · 35 comments · Fixed by bitwarden/clients#1935

Comments

@TetianaP
Copy link

TetianaP commented May 10, 2017

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
When using HashLocationStrategy and change url manually to the one, which is protected by CanActivate guard (this returns false and fire redirect to the other page), navigation events are called twice.

Expected behavior
All navigation event are called once only.

Minimal reproduction of the problem with instructions
Use the demo in http://plnkr.co/edit/4xlYD9wmetG5G0Dvjp0E?p=preview
Open window mode http://run.plnkr.co/plunks/4xlYD9wmetG5G0Dvjp0E/#/component-one
Click Guarded Component Two link - only one NavigationStart and NavigationCancel events logged in console.
Manually change url from /#/component-one to /#/component-two and hit enter - two pair of NavigationStart and NavigationCancel events logged in console.

What is the motivation / use case for changing the behavior?
As the duplication of navigation event causes guards logic to be executed twice (and this may contain API calls) - it is a performance issue. Potentially, if guard resolving depends on some conditions which are true after first failed attempt - the second one will be successful, however it is not desired behavior.

Please tell us about your environment:

  • Angular version: 4.1.0

  • Browser: Chrome 57

  • Language: TypeScript

@IAMtheIAM
Copy link

IAMtheIAM commented Oct 12, 2017

No activity or solution in months? This bug still exists. @jasonhodges Is this still on Angulars radar? Its a significant bug, in my opinion.

Duplicate issue at #18366

@lifenautjoe
Copy link

Getting five NavigationEnd events fired when

router.events.subscribe((val) => {
            if (val instanceof NavigationEnd) {
                console.log('NavigationEnd!');
            }
        });

@blackrek
Copy link

blackrek commented Feb 12, 2018

have same problem.
i planning update to 5 version, maybe this will help

@IraErshova
Copy link

Have the same problem. Use Angular version: 5.2.2 and Typescript 2.6.2

@aerlijman
Copy link

Same here with Angular 5.
Any update on this?
I pretty sure this issue is braking the back button click in the browser getting A->B->C and looping between B and C with the back button click in browser.

@harlenalvarez
Copy link

I have the same problem. Angular version 5.2.9

@Dhamu143
Copy link

is there any update ??

@padilla-jm
Copy link

I am able to work around this issue but I am also very interested in a fix.

@rinkeshmistry
Copy link

@jfox459 can u please provide your work around ?

@ohabash
Copy link

ohabash commented May 9, 2018

this worked for me

onRouteChange() {
	this._routeListener = this.router.events.subscribe((event) => ...);
}

ngOnDestroy() {
	this._routeListener.unsubscribe();
}

@russelporosky
Copy link

russelporosky commented May 29, 2018

This bug still exists with v6. I'm not using any onRouteChange methods or subscribing to the router events at any point, but it still fires twice if a user changes the hash portion of the URL.

The navigationStart event shows navigationTrigger: 'popstate' for the initial navigation change, then show navigationTrigger: 'hashchange' for the second one.

@shmoxy
Copy link

shmoxy commented Jul 22, 2018

I have the same issue in chrome but not in firefox, canActivate guard called twice, any solution ? :(

@rodmax
Copy link

rodmax commented Sep 4, 2018

i have the same issue as @metaloha described

In my case it is critical issue due to

  • i subscribe to route changes and in particular cases replace some query params,
  • then router triggers event with MY REPLACED params(all good)
  • and then(!!!) triggers route changes with olg URL(state), which sourced as "hashchange":

navigationTrigger sequence after i typing URL in browser navigation input

  • popstate
  • imperative - it is my router.navigate() call which lives in subsription to router.events
  • hashchange - unexpected event with MISSING state from my imperative change

so this behaviour brokes my logic

for now i use workaround and call router.navigate in setTimeout() callback but it seem not normal behaviour

@acautin
Copy link

acautin commented Oct 31, 2018

same use case as @rodmax, did you find a workaround without having to resort to settimeout ? also if the timeout is short I am still seeing the issue so redirections are slow now.

@BlindDespair
Copy link

BlindDespair commented Dec 21, 2018

We ran into same issue, we have a complex navigation logic with multiple options for redirection depending on conditions. And if something we try to navigate to does not exist then modal window is shown. But because of this hashChange it runs guard twice and therefore we get 2 error modals which is not nice, I still haven't found a way to work it around.

EDIT: wrapping imperative navigation call into setTImeout helped, but it's not really that nice, as I also had to wrap setTimeout in a promise.

@volodymyro-in6k
Copy link

Hi, any updates here?

We have the same issue.
Currently, like a workaround we set timeout. I do not consider it as the final decision.

Thanks!

@Suyashjc
Copy link

Hi, any updates here?

We have the same issue.
Currently, like a workaround we set timeout. I do not consider it as the final decision.

Thanks!

Any update with this issue?
If not, can you help with how and where have you used set timeout as a workaround?

Thanks!

@volodymyro-in6k
Copy link

@Suyashjc

Hi there, here is our case example:

@Injectable()
export class XYZGuard implements CanActivate, CanActivateChild {
  constructor(private service: SomeService, private router: Router) {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.service.observable$
      .pipe(
        tap(condition => {
          if (condition) {
            setTimeout(() => this.router.navigate('your path'));
          }
        })
      );
  }

  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.canActivate(childRoute, state);
  }
}

@edwardbrosens
Copy link

this worked for me

onRouteChange() {
	this._routeListener = this.router.events.subscribe((event) => ...);
}

ngOnDestroy() {
	this._routeListener.unsubscribe();
}

Issue still exists.
But this is the most clean working solution.

congaptit added a commit to congaptit/angular that referenced this issue Sep 27, 2019
Fix issue 16710 (angular#16710)
congaptit added a commit to congaptit/angular that referenced this issue Sep 28, 2019
Fix issue 16710. Navigation events are called twice when using HashLocationStrategy. When user change url manually in browser URL.
ex: Router A(component A) using navigateByUrl(in guard) to redirect to Router B(Component B). When user enter router A in browser URL. That follow will be call twice. Detail in issue 16710

angular#16710
@atscott
Copy link
Contributor

atscott commented Jun 20, 2020

Here is the same example but using 9.1.9:
https://stackblitz.com/edit/angular-ivy-rucgay

Yep, that's it. Not sure what happened there.

So I think I've pinpointed the issue. It's related to this bit of code in the scheduleNavigation which should catch the duplicate popstate and hashchange (you can ignore the comment about IE and edge, this is also happening on chrome).

// Because of a bug in IE and Edge, the location class fires two events (popstate and
// hashchange) every single time. The second one should be ignored. Otherwise, the URL will
// flicker. Handles the case when a popstate was emitted first.
if (lastNavigation && source == 'hashchange' && lastNavigation.source === 'popstate' &&
lastNavigation.rawUrl.toString() === rawUrl.toString()) {

However, because the duplicate navigations are scheduled in a setTimeout here

setTimeout(() => {
this.scheduleNavigation(rawUrlTree, source, state, {replaceUrl: true});
}, 0);

the first one executes and calls router.navigate from the guard before the second, duplicate navigation from hashchange is processed. This means the check above won't work, because another navigation got inserted between the two duplicates.

This could potentially be solved by adding similar double event guarding logic to the location subscription and do the check before scheduling the navigation. The check would have to be modified a bit (probably with a pairwise rxjs operator and saving the navigationId of the last navigation, as seen by the subscription).

@sumiflow
Copy link

Awesome to see an old issue acknowledge and found. Thanks for investigating.

@congaptit
Copy link

@atscott I dont have time to rewrite a test case as your comment but you can refer my code to resolve this problem
https://github.com/angular/angular/pull/32898/files

atscott added a commit to atscott/angular that referenced this issue Jun 22, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 22, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 22, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 22, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 22, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 24, 2020
This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in angular@eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in angular#16710: angular#16710 (comment)
This also partially addresses angular#13586 in fixing history for imperative
navigations that are cancelled by guards.
atscott added a commit to atscott/angular that referenced this issue Jun 24, 2020
This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in angular@eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in angular#16710: angular#16710 (comment)
This also partially addresses angular#13586 in fixing history for imperative
navigations that are cancelled by guards.
ngwattcos pushed a commit to ngwattcos/angular that referenced this issue Jun 25, 2020
…url (angular#37408)

This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in angular@eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in angular#16710: angular#16710 (comment)
This also partially addresses angular#13586 in fixing history for imperative
navigations that are cancelled by guards.

PR Close angular#37408
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 29, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jun 30, 2020
This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in angular@eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in angular#16710: angular#16710 (comment)
This also partially addresses angular#13586 in fixing history for imperative
navigations that are cancelled by guards.
atscott added a commit to atscott/angular that referenced this issue Jun 30, 2020
This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in angular@eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in angular#16710: angular#16710 (comment)
This also partially addresses angular#13586 in fixing history for imperative
navigations that are cancelled by guards.
alxhub pushed a commit that referenced this issue Jul 6, 2020
…url (#37716)

This PR changes the logic for determining when to skip route processing from
using the URL of the last attempted navigation to the actual resulting URL after
that transition.

Because guards may prevent navigation and reset the browser URL, the raw
URL of the previous transition may not match the actual URL of the
browser at the end of the navigation process. For that reason, we need to use
`urlAfterRedirects` instead.

Other notes:
These checks in scheduleNavigation were added in eb2ceff
The test still passes and, more surprisingly, passes if the checks are removed
completely. There have likely been changes to the navigation handling that
handle the test in a different way. That said, it still appears to be important
to keep the checks there in some capacity because it does affect how many
navigation events occur. This addresses an issue that came up in #16710: #16710 (comment)
This also partially addresses #13586 in fixing history for imperative
navigations that are cancelled by guards.

PR Close #37716
atscott added a commit to atscott/angular that referenced this issue Jul 7, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jul 7, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
atscott added a commit to atscott/angular that referenced this issue Jul 7, 2020
…correctly

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710
@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 Aug 13, 2020
profanis pushed a commit to profanis/angular that referenced this issue Sep 5, 2020
…correctly (angular#37674)

The current method of handling duplicate navigations caused by 'hashchange' and 'popstate' events for the same url change does not correctly handle cancelled navigations. Because `scheduleNavigation` is called in a `setTimeout` in the location change subscription, the duplicate navigations are not flushed at the same time. This means that if the initial navigation hits a guard that schedules a new navigation, the navigation for the duplicate event will not compare to the correct transition (because we inserted another navigation between the duplicates). See angular#16710 (comment)

Fixes angular#16710

PR Close angular#37674
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.