Como determinar o URL da página anterior no Angular?

104

Suponha que eu esteja atualmente na página que contém o URL /user/:id. Agora, a partir desta página, navego para a próxima página :id/posts.

Agora existe uma maneira, para que eu possa verificar qual é a URL anterior, ou seja /user/:id.

Abaixo estão minhas rotas

export const routes: Routes = [
  { 
    path: 'user/:id', component: UserProfileComponent
  },
  {  
    path: ':id/posts', component: UserPostsComponet 
  }
];
Chandra Shekhar
fonte

Respostas:

83

Você pode se inscrever para mudanças de rota e armazenar o evento atual para que possa usá-lo quando o próximo acontecer

previousUrl: string;
constructor(router: Router) {
  router.events
  .pipe(filter(event => event instanceof NavigationEnd))
  .subscribe((event: NavigationEnd) => {
    console.log('prev:', event.url);
    this.previousUrl = event.url;
  });
}

Veja também Como detectar uma mudança de rota no Angular?

Günter Zöchbauer
fonte
12
Obrigado @ Günter Você sempre salva meu dia.
Chandra Shekhar
34
Isso não lista a rota anterior para mim, apenas a rota atual.
David Aguirre
2
Depende do que você espera. A primeira vez é nullporque não existe uma rota anterior. Você também precisa fazer isso no roteador raiz, caso contrário, você só obterá quando navegar entre as rotas secundárias deste componente.
Günter Zöchbauer
8
Isso não fornece o url anterior quando o construtor é executado pela primeira vez.
Ekaitz Hernandez Troyas
9
Qual valor você espera como url anterior quando o construtor é executado pela primeira vez?
Günter Zöchbauer
114

Talvez todas as outras respostas sejam para angular 2.X.

Agora não funciona para o angular 5.X. Estou trabalhando nisso.

com apenas NavigationEnd, você não pode obter url anterior.

porque o roteador funciona de "NavigationStart", "RoutesRecognized", ..., para "NavigationEnd".

Você pode verificar com

    router.events.forEach((event) => {
  console.log(event);
});

Mas ainda assim você não pode obter url anterior, mesmo com "NavigationStart".

Agora você precisa usar os pares.

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

constructor(private router: Router) {
  this.router.events
    .filter(e => e instanceof RoutesRecognized)
    .pairwise()
    .subscribe((event: any[]) => {
      console.log(event[0].urlAfterRedirects);
    });
}
    

Com o emparelhamento, você pode ver de e para onde é o url.

"RoutesRecognized" é a etapa de mudança da origem para o URL de destino.

então filtre-o e obtenha o URL anterior dele.

Por último mas não menos importante,

coloque este código no componente pai ou superior (por exemplo, app.component.ts)

porque este código dispara após terminar o roteamento.

Atualizar angular 6+

O events.filterdá erro porque o filtro não faz parte dos eventos, então mude o código para

import { filter, pairwise } from 'rxjs/operators';

this.router.events
.pipe(filter((evt: any) => evt instanceof RoutesRecognized), pairwise())
.subscribe((events: RoutesRecognized[]) => {
  console.log('previous url', events[0].urlAfterRedirects);
  console.log('current url', events[1].urlAfterRedirects);
});
BYUNGJU JIN
fonte
2
Implementado como um serviço e funciona muito bem. Estou usando o angular 6.1.7.
A. El Idrissi
5
@ tjvg1991 Atualizar página significa que você perdeu dados da memória. se você mantiver os dados anteriores, precisará usar localStorage ou cookie. (salvar dados no seu local, não na memória)
BYUNGJU JIN
@BYUNGJUJIN Obrigado por isso!
john
1
@BYUNGJUIN armazena o valor de retorno de subscribe()em um campo e o chama unsubscribe()em ngOnDestroy(). Deve haver severs. perguntas com exemplos sobre cancelamento de inscrição aqui no SO.
Günter Zöchbauer
1
@app, verifique isto para cancelar a inscrição: malcontentboffin.com/2017/12/…
BYUNGJU JIN
51

Crie um serviço injetável:

import { Injectable } from '@angular/core';
import { Router, RouterEvent, NavigationEnd } from '@angular/router';

 /** A router wrapper, adding extra functions. */
@Injectable()
export class RouterExtService {

  private previousUrl: string = undefined;
  private currentUrl: string = undefined;

  constructor(private router : Router) {
    this.currentUrl = this.router.url;
    router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {        
        this.previousUrl = this.currentUrl;
        this.currentUrl = event.url;
      };
    });
  }

  public getPreviousUrl(){
    return this.previousUrl;
  }    
}

Em seguida, use-o onde você precisar. Para armazenar a variável atual o mais rápido possível, é necessário usar o serviço no AppModule.

// AppModule
export class AppModule {
  constructor(private routerExtService: RouterExtService){}

  //...

}

// Using in SomeComponent
export class SomeComponent implements OnInit {

  constructor(private routerExtService: RouterExtService, private location: Location) { } 

  public back(): void {
    this.location.back();
  }

  //Strange name, but it makes sense. Behind the scenes, we are pushing to history the previous url
  public goToPrevious(): void {
    let previous = this.routerExtService.getPreviousUrl();

    if(previous)
      this.routerExtService.router.navigateByUrl(previous);
  }

  //...

}
Juliano
fonte
2
Acho que essa é a solução mais elegante. Tente mesclar este código com o novo filtro e solução de pares de: stackoverflow.com/a/35287471/518879
danger89
2
Ps. Não se esqueça de adicionar este RouterExtService ao apps-routing.module.ts (no meu caso), assim:@NgModule({ ..., providers: [RouterExtService]}) export class AppRoutingModule { }
danger89
Ok, há um grande problema com essa solução de serviço .. No meu caso chamo o routerExtService.getPreviousUrl()método no construtor de um serviço usado em um componente. Por algum motivo, ele é chamado antes da atualização real. O que significa que temos uma dependência de tempo! Eu acho que é muito mais fácil usar o Assunto.
hazard89,
Bem, funcionou bem para mim em um pequeno projeto. Talvez precise de alguns ajustes para atender às suas necessidades. Você resolveu o problema?
Juliano
Atualmente estou usando os chamados parâmetros de matriz de URL para 'armazenar' meu estado em minha URL. Por padrão, a URL do navegador armazena o estado agora ao usar o botão Voltar. let params = new HttpParams({fromString: retrieveURL}).set('name', 'victor') const paramsObject = params.keys().reduce((obj, key) => { obj[key] = params.get(key) return obj }, {}) this.router.navigate([paramsObject], { relativeTo: this.route })
hazard89
20

Código atualizado do Angular 6 para obter o URL anterior como string.

import { Router, RoutesRecognized } from '@angular/router';
import { filter, pairwise } from 'rxjs/operators';


export class AppComponent implements OnInit {

    constructor (
        public router: Router
    ) {
    }

    ngOnInit() {
        this.router.events
            .pipe(filter((e: any) => e instanceof RoutesRecognized),
                pairwise()
            ).subscribe((e: any) => {
                console.log(e[0].urlAfterRedirects); // previous url
            });
    }
Franklin Pious
fonte
Isso retorna a url que foi bloqueada por um guarda, existe uma maneira de obter apenas a url anterior que foi ativada (não bloqueada pelo guarda)?
Exocomp de
1
Alguma dica sobre a melhor maneira de cancelar a assinatura do roteador?
j4v1
Trabalho! Eu realmente não sei por que "NavigationEnd" não funciona
davidwillianx
13

Isso funcionou para mim nas versões angular> = 6.x:

this.router.events
            .subscribe((event) => {
              if (event instanceof NavigationStart) {
                window.localStorage.setItem('previousUrl', this.router.url);
              }
            });
Praveen Pandey
fonte
11

Estou usando o Angular 8 e a resposta de @franklin-pious resolve o problema. No meu caso, obter o url anterior dentro de uma assinatura causa alguns efeitos colaterais se estiver anexado a alguns dados na visualização.

A solução alternativa que usei foi enviar o url anterior como um parâmetro opcional na navegação da rota.

this.router.navigate(['/my-previous-route', {previousUrl: 'my-current-route'}])

E para obter esse valor no componente:

this.route.snapshot.paramMap.get('previousUrl')

this.router e this.route são injetados dentro do construtor de cada componente e são importados como membros @ angular / router.

import { Router, ActivatedRoute }   from '@angular/router';
Victor Oliveira
fonte
10

Angular 8 e rxjs 6 na versão 2019

Eu gostaria de compartilhar a solução com base em outras grandes soluções.

Primeiro, faça um serviço para escutar as alterações de rotas e salve a última rota anterior em um Assunto de comportamento, em seguida, forneça este serviço no app.component principal no construtor e use este serviço para obter a rota anterior que você deseja, sempre que quiser.

Caso de uso: você deseja redirecionar o usuário para uma página de anúncio e, em seguida, redirecioná-lo automaticamente para o lugar de onde ele veio, então você precisa da última rota anterior para fazer isso.

// service : route-events.service.ts

import { Injectable } from '@angular/core';
import { Router, RoutesRecognized } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter, pairwise } from 'rxjs/operators';
import { Location } from '@angular/common';

@Injectable()
export class RouteEventsService {

    // save the previous route
  public previousRoutePath = new BehaviorSubject<string>('');

  constructor(
    private router: Router,
    private location: Location
  ) {

    // ..initial prvious route will be the current path for now
    this.previousRoutePath.next(this.location.path());


    // on every route change take the two events of two routes changed(using pairwise)
    // and save the old one in a behavious subject to access it in another component
    // we can use if another component like intro-advertise need the previous route
    // because he need to redirect the user to where he did came from.
    this.router.events.pipe(
      filter(e => e instanceof RoutesRecognized),
      pairwise(),
        )
    .subscribe((event: any[]) => {
        this.previousRoutePath.next(event[0].urlAfterRedirects);
    });

  }
}

fornecer o serviço em app.module

  providers: [
    ....
    RouteEventsService,
    ....
  ]

Injete-o em app.component

  constructor(
    private routeEventsService: RouteEventsService
  )

finalmente use a rota anterior salva no componente que você deseja

  onSkipHandler(){
    // navigate the user to where he did came from
    this.router.navigate([this.routeEventsService.previousRoutePath.value]);
  }
Louay Alosh
fonte
Isso funciona muito bem. Mas eu tenho uma pergunta rápida. Você sempre cancela a inscrição?
w0ns88
adicionar take (1) assim -> emparelhados (), take (1)). subscrever ((e: qualquer)
Mukus
1
Observe que se você usar @Injectable({ providedIn: 'root' })o serviço é carregado automaticamente no módulo raiz (AppModule) do projeto e, portanto, você não precisa fornecê-lo manualmente ao app.module. Veja a documentação para detalhes. Não é necessário cancelar a assinatura do roteador Observáveis ​​conforme declarado nesta resposta
Hkidd
7

PARA ANGULAR 7+

Na verdade, desde o Angular 7.2 não há necessidade de usar um serviço para salvar a url anterior. Você poderia apenas usar o objeto de estado para definir o último url antes de vincular à página de login. Aqui está um exemplo de um cenário de login.

@Component({ ... })
class SomePageComponent {
  constructor(private router: Router) {}

  checkLogin() {
    if (!this.auth.loggedIn()) {
      this.router.navigate(['login'], { state: { redirect: this.router.url } });
    }
  }
}
@Component({...})
class LoginComponent {
  constructor(private router: Router) {}

  backToPreviousPage() {
    const { redirect } = window.history.state;

    this.router.navigateByUrl(redirect || '/homepage');
  }
}
----------------

Além disso, você também pode passar os dados no modelo:

@Component({
  template: '<a routerLink="/some-route" [state]="{ redirect: router.url}">Go to some route</a>'
})
class SomePageComponent {
  constructor(public router: Router) {}
}
Knoefel
fonte
3

@ GünterZöchbauer também você pode salvá-lo no armazenamento local, mas eu não prefiro) melhor salvar no serviço e obter esse valor de lá

 constructor(
        private router: Router
      ) {
        this.router.events
          .subscribe((event) => {
            if (event instanceof NavigationEnd) {
              localStorage.setItem('previousUrl', event.url);
            }
          });
      }
vladymy
fonte
3

Você pode usar o Local conforme mencionado aqui .

Este é o meu código se o link abrir em uma nova guia

navBack() {
    let cur_path = this.location.path();
    this.location.back();
    if (cur_path === this.location.path())
     this.router.navigate(['/default-route']);    
  }

Importações necessárias

import { Router } from '@angular/router';
import { Location } from '@angular/common';
Waleed Emad
fonte
0

Bastante simples usando previousNavigationobjeto:

this.router.events
  .pipe(
    filter(e => e instanceof NavigationEnd && this.router.getCurrentNavigation().previousNavigation),
    map(() => this.router.getCurrentNavigation().previousNavigation.finalUrl.toString()),
  )
  .subscribe(previousUrl => {}); 
Dmitrij Kuba
fonte
0

Tive dificuldade para acessar o url anterior dentro de um guarda.
Sem implementar uma solução personalizada, esta está funcionando para mim.

public constructor(private readonly router: Router) {
};

public ngOnInit() {
   this.router.getCurrentNavigation().previousNavigation.initialUrl.toString();
}

O url inicial será a página de url anterior.

C0ZEN
fonte
0

Esta solução simples funcionou para mim.

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

export class TempComponent {
    constructor(private router: Router) {
        this.router.events.pairwise().subscribe((event) => {
            console.log(event); // NavigationEnd will have last and current visit url
        });
    };
}
Sahil Ralkar
fonte
-2

Toda a ANSWER acima será carregada URL várias vezes. Se o usuário visitou qualquer outro componente também, esse código será carregado.

Portanto, é melhor usar o conceito de criação de serviço. https://community.wia.io/d/22-access-the-previous-route-in-your-angular-5-app

Isso funcionará bem em todas as versões do Angular. (certifique-se de adicioná-lo à matriz de fornecedores em seu arquivo app.module!)

ABHILASHA KM
fonte
-2

Usando pairwise do rxjx, você pode conseguir isso mais facilmente. importar {filtro, par a par} de 'rxjs / operadores';

previousUrl: string;
constructor(router: Router) {
router.events
  .pipe(filter((evt: any) => evt instanceof RoutesRecognized), pairwise())
  .subscribe((events: RoutesRecognized[]) => {
  console.log('previous url', events[0].urlAfterRedirects);
  console.log('current url', events[1].urlAfterRedirects);
  this.previousUrl = events[0].urlAfterRedirects;
});

}

Ashraf Ali
fonte
-6

Tive um problema semelhante quando quis voltar à página anterior. A solução foi mais fácil do que eu poderia imaginar.

<button [routerLink]="['../']">
   Back
</button>

E ele retorna ao url pai. Espero que ajude alguém;)

DiPix
fonte
Isso não vai funcionar, você está dizendo para subir no caminho do roteador, ao invés do url anterior, como o OP declarou.
Frederic Yesid Peña Sánchez,
Isso não funciona se o seu url for complexo com parâmetros ou não tiver o mesmo caminho do pai. Funciona apenas se você quiser voltar de "algo / pai / filho" para "algo / pai".
A. El Idrissi