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

Ability to check for previous route with router.events.subscribe #11268

Closed
burritobill opened this issue Sep 2, 2016 · 31 comments
Closed

Ability to check for previous route with router.events.subscribe #11268

burritobill opened this issue Sep 2, 2016 · 31 comments

Comments

@burritobill
Copy link

burritobill commented Sep 2, 2016

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

[ ] bug report => search github for a similar issue or PR before submitting
[X] 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
The NavigationStart event object only returns the target url.

Expected/desired behavior
Have the event also return the previous url

What is the motivation / use case for changing the behavior?
I am having to call window.scrollTo(0,0) on every route call so that the new route doesn't load at a random point on the page. But I don't want this to happen on every route, and in some cases I want this to not happen when the routes are both children of the same component route.

Please tell us about your environment:

  • Angular version: 2.0.0-rc.5
  • Browser: [all ]
  • Language: [all]
@DzmitryShylovich
Copy link
Contributor

You can use pairwise method to get previous and the current value

@burritobill
Copy link
Author

burritobill commented Sep 5, 2016

Thank you this worked perfectly.

Code for reference.

import 'rxjs/add/operator/pairwise';
import {Router} from '@angular/router;

export class AppComponent {
    constructor(private router: Router) {
        this.router.events.pairwise().subscribe((e) => {
            console.log(e);
        }
    });
}

@felipedrumond
Copy link

I'm having problem with this solution. The code inside the subscribe does not run when I enter the component for the very first time, that is when I need to get the previous url.

@burritobill
Copy link
Author

Is this code in your main app component? It shouldn't be in a child component.

@jjlorenzo
Copy link

jjlorenzo commented Jan 10, 2017

tl;dr:

I subscribe to this events in the AppComponent.ngOnInit.

My first solution was the following but it doesn't works, what I really need an operator like pairwise but the group of 3, let my see is I found one, I'm very new to Rx:

Instead of pairwise I buffer by 6 to group all the related event objects

this.router.events.bufferCount(6).subscribe((e: any[]) => {
  console.log('previous', e[1].urlAfterRedirects);
});
e = [
  NavigationStart, RoutesRecognized, NavigationEnd // previous route info
  NavigationStart, RoutesRecognized, NavigationEnd // current route info
]

And I use the RoutesRecognized objects because they contain the redirect info.

@DzmitryShylovich
Copy link
Contributor

DzmitryShylovich commented Jan 10, 2017

If you only useRoutesRecognized there's no need to buffer all 6 events. You can just filter RoutesRecognized and use pairwise.

@jjlorenzo
Copy link

After following your recommendation. This is my working code.

import { Component }           from '@angular/core';
import { OnInit }              from '@angular/core';
import { Router }              from '@angular/router';
import { LocalStorageService } from 'angular-2-local-storage';


@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`,
})
export class AppComponent implements OnInit {

  constructor(
    private router: Router,
    private storage: LocalStorageService,
  ) { }

  ngOnInit() {
    this.router.events
      .filter(e => e.constructor.name === 'RoutesRecognized')
      .pairwise()
      .subscribe((e: any[]) => {
        this.storage.set('referrer', e[0].urlAfterRedirects);
      });
  }

}

Thank you very much!

@DzmitryShylovich
Copy link
Contributor

import { RoutesRecognized} from '@angular/router';
.filter(e => e instanceof RoutesRecognized)

@jjlorenzo
Copy link

Thanks!, fixed in my code.
I don't know if it is good to edit my previous post to indicate the problems, I think it is easy to follow as it is now and to get the final version with your recommendations.

@brechtbilliet
Copy link
Contributor

Isn't it sad that we need the localstorage to fetch the last route in a component? I guess this should be a pretty simple use case.

@jjlorenzo
Copy link

This was my first approach, using the local storage, then I realize that persisting this value wasn't necessary, so I convert into a service

@PostImpatica
Copy link

I just wanted to store previous route, this worked for me although I'm sure I'm sure I'm missing something from above.

this.router.events.pairwise().takeUntil(this.destroyed$).subscribe((e) => {
  if (e[0]['urlAfterRedirects'] && !e[0]['state']) {
    this.store.dispatch(new statusActions.SetPreviousRouteAction(e[0].url));
  }});

@vitorrd
Copy link

vitorrd commented Mar 7, 2017

@helzgate You should be filtering your Router events to look for the event that has the property you seek, urlAfterRedirects, which is RoutesRecognized. This can be done with:

event instanceof RoutesRecognized

On topic: Just highlighting the fact that this is far from an ideal solution. What you're suggesting here is keeping track of the second to last route navigated to, which is not the same as the second to last route in navigation history. Use navigateByUrl with replaceUrl: true and your history is no longer accurate.

@vsavkin Could you please point us towards a conclusive solution? Is there a way to do this in a consistent manner?

@ilpoo
Copy link

ilpoo commented Apr 3, 2017

Am I missing something really obvious here?

import { Component, OnInit } from '@angular/core';
import { Router, RoutesRecognized } from '@angular/router';
import { LocalStorageService } from 'angular-2-local-storage';
import 'rxjs/add/operator/pairwise';

@Component({
  selector: 'app',
  template: `
    <Content>
      <router-outlet></router-outlet>
    </Content>
  `,
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private storage: LocalStorageService,
  ) { }

  ngOnInit() {
    this.router.events
      .filter((e: any) => e instanceof RoutesRecognized)
      .pairwise()
      .subscribe((e: any) => {
        console.log(e);
      });
  }
}

Gives me:

ERROR TypeError: this.router.events.filter is not a function

@DzmitryShylovich
Copy link
Contributor

import 'rxjs/add/operator/filter';

@HunderlineK
Copy link

HunderlineK commented May 8, 2017

I needed to implement a return link and it sent me down the RxJs rabbit hole. The implementation with the pairwise will not work if the user doesn't trigger navigation event at least two times and may cause a small bug.

page 1 -> page 2

  • navigation event triggered, but event Observable has only 1 value and thus pairwise does not emit
    ---> User cannot go back to page 1

page 2 -> page 3

  • event Observable now has 2 elements and pairwise emits

A better implementation could potentially use bufferCount (RxJs 5) if NavigationStart triggered on the page that links to the other page (page 1 and then page 2)? However, it seems that NavigationStart doesn't trigger on the page that links to the other page, but to the destination page (page 2). Is that the expected behaviour? If so, then what is the difference between NavigationStart and NavigationEnd?

import { Component, OnInit } from '@angular/core';
import { NavigationStart, Router }   from '@angular/router';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/bufferCount';

@Component({
  selector: 'app-profile-details',
  templateUrl: './profile-details.component.html',
  styleUrls: ['./profile-details.component.css']
})
export class ProfileDetailsComponent implements OnInit {

  constructor(
    private router: Router,
  ) { }

  ngOnInit(): void {
    this.router.events.filter(e => e instanceof NavigationStart )
      .bufferCount(1,1).subscribe(e => console.log(e[0]['url']));
  }

}

Pairwise documentation:

 * The Nth emission from the source Observable will cause the output Observable
 * to emit an array [(N-1)th, Nth] of the previous and the current value, as a
 * pair. For this reason, `pairwise` emits on the second and subsequent
 * emissions from the source Observable, but not on the first emission, because
 * there is no previous value in that case.

which is because of this if check in the code:

        if (this.hasPrev) {
            this.destination.next([this.prev, value]);
        }
        else {
            this.hasPrev = true;
        }

The pairwise can be hacked to return [undefined, element] event if there is only 1 element in the buffer easily

        if (this.hasPrev) {
            this.destination.next([this.prev, value]);
        }
        else {
            this.destination.next([undefined, value]);
            this.hasPrev = true;
        }

Maybe I should take this over to RxJs team?

@tytskyi
Copy link

tytskyi commented May 8, 2017

@HoumanKml

I needed to implement a return link

I think you need no Rx, just this service:

https://angular.io/docs/ts/latest/api/common/index/Location-class.html#!#back-anchor

@HunderlineK
Copy link

HunderlineK commented May 8, 2017

@tytskyi
Thanks for the link! I should have explained my use case more, as I am not sure if location.back() would work for me: I want the return button to appear only if the user is redirected to the profile details from the profile page; however, I don't want it to appear if the user lands on the profile details page from somewhere else [e.g. searching for a specific profile from the main page].

I admit there are other possible solutions to solve this and similar use cases and so adding a new feature or modifying the current behaviour of the navigationstart is not urgent. For example if one is using redux, there can be a location history array that is updated each time a navigation action is issued -- Though I don't like this particular approach because a global state would be redundant, as the navigation event observable is exactly that global history array [and working with RxJs is more fun :D]

@mischkl
Copy link

mischkl commented May 30, 2017

Is there another issue that considers adding information about the previous route to the ActivatedRoute? This would be the most flexible and easy-to-use solution, since it can be taken advantage of e.g. in Guards.

@miteshravindra
Copy link

Hi all, this is exactly what i was looking for can someone help me understand how to use the RoutesRecognized properties so that I can track the routes and use that information to help the user navigate to the previous url?I tried logging the RouteRecognized but it is logging a function named RoutesRecognized with four properties in it
1.) id
2.)url
3.) urlAfterRedirects
4.)state

Thanks.

@evgenyfedorenko
Copy link

I am getting multiple events of the same type when subscribing to events upon changing the route: for example second time I change the route I will get 2 NavigationStart events, 2 NavigationEnd events etc. When I change the route for the 3rd time then there are 3 events of each type. I don't think that is expected - is there a problem in my app somewhere which fires those additional events?

@dlarr
Copy link

dlarr commented Nov 10, 2017

@deli6z : same for me

this.router.events.filter(e => e instanceof NavigationEnd).pairwise().subscribe((e) => {
        console.log('NAVIGATION PREVIOUS => ',e);
});

If i navigate back and forth between the 2 components, I get multi lines :(
image

@hillangat
Copy link

hillangat commented Nov 25, 2017

@deli6z , I used it like this and it solved my problem.
Please try it out at your end and let me know how it goes:

this.router.events .filter(event => event instanceof NavigationEnd) .pairwise() .first() .subscribe...........

This will emit the first pair. If it's not a pair, it won't emit anything as per my observation.

@hillangat
Copy link

@DzmitryShylovich
Could you please let me know how you hacked your way out of this?

if (this.hasPrev) { this.destination.next([this.prev, value]); } else { this.destination.next([undefined, value]); this.hasPrev = true; }

I need to do pairwise but I need to return the first one even if there is no second value

@tatsujb
Copy link

tatsujb commented Feb 16, 2018

@HunderlineK you're the one true messiah and @DzmitryShylovich i dunno wtf is going on with all your suggestions being systematically unrunnable I'm going to assume it's just deprecation that made it this way in angular v.5. and it worked before, but these comments should be removed or edited there's people being thrown off course by them.

@Santosh-Lodhi
Copy link

Hi

I have error in below code in Angular 5, Can anyone help

this.router.events
      .filter(e => e instanceof RoutesRecognized)
      .pairwise()
      .subscribe((e: any[]) => {
        console.log('referrer', e[0].urlAfterRedirects);
});

Error :

ERROR Error: Uncaught (in promise): TypeError: this.router.events.filter(...).pairwise is not a function
TypeError: this.router.events.filter(...).pairwise is not a function
at new SessionComponent (session.component.ts:42)
at createClass (core.js:12449)
at createDirectiveInstance (core.js:12284)
at createViewNodes (core.js:13742)......

@Santosh-Lodhi
Copy link

I want to get previous url on navigate new page
Error resolve after adding below rxjs import, but functionality not achieved.

import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/pairwise';

@tatsujb
Copy link

tatsujb commented May 25, 2018

@Santosh-Lodhi

ngOnInit(){
    this._router.events.filter(e => e instanceof NavigationStart )
      .bufferCount(2,1).subscribe(e => {
        if(e !== null && e !== undefined) this.orginalPath = e[0]['url'].substr(1)
    });
  }

@lazycodie
Copy link

Can anybody tell me how to route from the same component in the next time?
And please look at my code and let me know if anyone have solution for this.
CODE:
let navigationExtras: NavigationExtras = {
queryParams: {
"page_id": pid (Id to route to some other component)
}
}
this.router.navigate(["/pages/blocks"], navigationExtras);

@BBlackwo
Copy link
Contributor

FYI for RxJS v6+ you need to use the .pipe() function, so:

router.events
  .pipe(
    filter(e => e instanceof NavigationEnd),
    pairwise() // check it's second route load
  )
  .subscribe((e: any[]) => {
    console.log(e);
  });

@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 Sep 15, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests