Roteamento no Angular: rotas, links e guards

Rocketseat

Rocketseat

13 min de leitura
roteamento-no-angular
Imagina que sua aplicação Angular está crescendo e você quer oferecer uma jornada fluida para quem a utiliza. É aí que entra o roteamento Angular! O sistema de rotas que guia os usuários pelas páginas da aplicação de forma clara e segura. Sem um roteamento bem planejado, a experiência seria como navegar sem mapa no ecossistema da aplicação. Com rotas bem definidas, você impulsiona a experiência do dev e de quem usa a aplicação, conectando tudo no ecossistema Angular.
Bora codar e entender na prática como configurar o Router, criar rotas (básicas e filhas), navegar com routerLink e proteger áreas com guards. Curtiu? Então se liga no conteúdo a seguir!

Configuração inicial do roteamento no Angular

Antes de sair criando páginas, precisamos preparar o terreno para o roteamento. No Angular, o roteamento é habilitado ao definirmos um conjunto de rotas e informarmos à aplicação que usaremos o Router. Isso geralmente envolve importar o módulo de rotas e registrar as rotas principais da aplicação. Usamos o método RouterModule.forRoot para configurar as rotas na raiz do app, o que fornece todos os serviços e diretivas necessários para o roteamento e já executa a primeira navegação de acordo com a URL do navegador.

RouterModule.forRoot e rotas básicas

O primeiro passo é definir as rotas básicas. Vamos criar um arquivo (por exemplo, app.routes.ts) com um array de rotas da aplicação. Cada rota mapeia um caminho (URL) a um componente Angular que deve ser exibido. Por exemplo, podemos ter uma rota para a página inicial, outra para listagem de desenvolvedores e outra para detalhes de um desenvolvedor específico. Veja um exemplo de definição de rotas:
// app.routes.ts import { Routes } from '@angular/router'; import { HomeComponent } from './home.component'; import { DevListComponent } from './dev-list.component'; import { DevDetailComponent } from './dev-detail.component'; import { NotFoundComponent } from './not-found.component'; export const routes: Routes = [ { path: '', component: HomeComponent }, // rota padrão (home) { path: 'devs', component: DevListComponent }, // lista de devs { path: 'devs/:id', component: DevDetailComponent }, // detalhes do dev com id { path: '**', component: NotFoundComponent } // wildcard -> 404 ];
No exemplo acima, configuramos quatro rotas. A rota com path: '' serve como página inicial (padrão) da aplicação, exibindo o HomeComponent. A rota 'devs' mostra a lista de devs (DevListComponent), e 'devs/:id' exibe os detalhes de um dev específico (DevDetailComponent), aproveitando um parâmetro de rota :id. Por fim, path: '**' define uma rota coringa (wildcard) que captura qualquer URL não correspondida pelas rotas anteriores e exibe o NotFoundComponent, isso será nossa página 404 (não encontrada). Essa rota wildcard deve ser definida por último no array de rotas, pois ela vai pegar tudo que não casou com rotas definidas acima.
Com as rotas definidas, precisamos informar ao Angular para utilizá-las. Para isso, abrimos o módulo principal (geralmente AppModule ou um módulo de rotas separado) e importamos o RouterModule.forRoot(routes). Isso registra nossas rotas na aplicação e garante que o serviço de Router e as diretivas do Angular (como <router-outlet> e routerLink) estejam disponíveis em toda a aplicação. Após essa configuração inicial, quando a aplicação for iniciada, o Router vai verificar a URL atual do navegador e navegar automaticamente para o componente correspondente (por exemplo, carregar o HomeComponent se a URL for a raiz / da aplicação).

Página padrão e 404

Notou que incluímos duas rotas especiais no array: uma com path: '' (string vazia) e outra com path: '**'. A primeira representa a rota padrão, ou seja, quando a URL for exatamente a raiz da aplicação, ela carrega o componente configurado (no nosso caso, o HomeComponent). É comum usarmos path: '' para definir qual página deve aparecer inicialmente ou quando a navegação não especifica nenhuma rota (como uma home page).
Já a rota com ** é o curinga para capturar tudo que não foi correspondido antes. Essa é uma estratégia para mostrar uma página de "Not Found" (404) ao usuário, em vez de deixá-lo perdido. Quando alguém digita ou navega para uma rota que não existe, o Angular Router vai cair nessa rota wildcard e renderizar o NotFoundComponent. Assim, podemos criar uma página 404 amigável dizendo que aquela página não foi encontrada, e talvez oferecer um link para voltar à home. Ter uma rota 404 garante que a jornada do user não termine em uma tela em branco, sempre haverá um caminho de volta.
Com a configuração básica pronta, nossa aplicação já entende para quais componentes navegar conforme a URL muda. Mas como fazemos essas URLs mudarem? É hora de ver como navegar entre as rotas.

Navegação

Configurar rotas é apenas o primeiro passo. Também precisamos de formas de navegar entre elas, seja através de links na interface ou via código, em resposta a ações do usuário. O Angular oferece duas abordagens principais para navegação:
  • Links declarativos no template, usando a diretiva routerLink em elementos HTML (como um <a> ou <button>), para navegação via cliques.
  • Navegação imperativa via código, usando os métodos do objeto Router (como router.navigate), para navegar programaticamente, por exemplo, depois de salvar um formulário ou em alguma lógica de negócio.
Vamos explorar cada uma dessas formas, incluindo como passar parâmetros de rota e query params em cada caso.

routerLink e query params

A forma mais comum de criar navegação é usando links no template com routerLink. Essa diretiva do Angular permite ligar um elemento clicável a uma rota da aplicação. Diferente de um link HTML tradicional (<a href="...">), o routerLink evita o reload da página e integra com o roteamento interno do Angular.
Um exemplo simples de menu de navegação em um componente poderia ser:
<!-- menu.component.html --> <nav> <a routerLink="/">Início</a> <a routerLink="/devs">Devs</a> </nav>
No código acima, temos dois links: "Início" aponta para a raiz / (carregando o HomeComponent), e "Devs" aponta para /devs (carregando o DevListComponent). Quando o usuário clica nesses links, o Angular intercepta o evento e chama o Router internamente para carregar o componente correspondente, em vez de fazer um refresh completo da página. O <router-outlet> em nosso componente principal (AppComponent) é onde o conteúdo da rota escolhida será exibido.
Mas e se precisarmos passar parâmetros ou queries na URL através do link? O routerLink permite isso também. Para rotas que possuem parâmetros de rota (como /devs/:id), podemos usar o link parameters array, que é basicamente, passar um array no routerLink contendo o caminho e os parâmetros. Por exemplo, para criar um link para detalhes do dev de id 256, faríamos:
<a [routerLink]="['/devs', 256]">Ver detalhes do Dev 256</a>
O Angular vai construir a URL combinando /devs com o parâmetro 256, resultando em /devs/256. Essa sintaxe de array é recomendada pelo Angular para garantir que os parâmetros sejam inseridos corretamente na URL. Da mesma forma, se tivéssemos rotas filhas, poderíamos listar vários segmentos no array.
Para query params (parâmetros de consulta opcionais na URL, após ?), usamos a propriedade [queryParams] no elemento. Suponha que queremos linkar para /devs filtrando por devs "juniores". Podemos fazer:
<a [routerLink]="['/devs']" [queryParams]="{ exp: 'junior' }">Devs Juniores</a>
Ao clicar, a URL navegada seria /devs?exp=junior. Os query params são opcionais e podem ser lidos pelo componente de destino (neste caso, DevListComponent) via ActivatedRoute. Você também pode combinar parâmetros de rota e query params juntos. Por exemplo, um link para /devs/256?ref=menu seria construído com [routerLink]="['/devs', 256]" e [queryParams]="{ ref: 'menu' }".
Lembre-se de que, ao utilizar routerLink, não precisamos colocar # na URL nem nada do tipo. O Angular Router, por padrão, usa HTML5 pushState para navegar sem recarregar a página, criando URLs "limpas" (sem hash). Assim, routerLink="/" funciona como esperado se seu app estiver configurado corretamente (não esqueça de ter <base href="/"> no index.html para o roteamento funcionar direitinho).

Navegação programática com Router

Nem sempre a navegação acontece por um clique direto do usuário em um link. Muitas vezes, precisamos navegar em resposta a alguma lógica de negócio ou interação dinâmica, por exemplo, após realizar o login, queremos enviar a pessoa para a página principal; ou depois de salvar um cadastro, redirecionar para a lista. Nesses casos, usamos o objeto Router do Angular para navegar via código (imperative navigation).
O Router pode ser injetado em qualquer componente ou serviço. Uma vez com ele, usamos métodos como navigate() para instruir a mudança de rota. Exemplo de uso em um componente:
import { Component } from '@angular/core'; import { Router } from '@angular/router'; @Component({...}) export class DevActionsComponent { constructor(private router: Router) {} irParaLista(): void { // navegação simples para a rota '/devs' this.router.navigate(['/devs']); } verDetalhesDev(id: string): void { // navegação com parâmetro de rota e query params this.router.navigate( ['/devs', id], { queryParams: { ref: 'preview' } } ); } }
No método irParaLista, usamos this.router.navigate(['/devs']) para ir à rota de listagem de devs programaticamente. Já em verDetalhesDev, passamos um array com o caminho e o id do dev, e um objeto de opções com queryParams. Assim, se id = "256", a navegação vai para /devs/256?ref=preview.
Essa forma imperativa de navegar é muito poderosa. Podemos, por exemplo, decidir para onde navegar com base em condições: após login bem-sucedido, navigate(['/dashboard']); se não autenticado, navigate(['/login']), e assim por diante. Vale notar que o método navigate retorna uma Promise, caso você precise saber quando a navegação terminou (por exemplo, para carregar dados após entrar na página). Além disso, existem outras opções como replaceUrl, state e queryParamsHandling que podem ser usadas conforme a necessidade, mas o exemplo acima cobre os cenários mais comuns.
Com routerLink e o objeto Router, você tem total controle da navegação, seja declarativa ou programática. Agora, vamos ver como organizar melhor as rotas quando a aplicação começa a ficar maior, usando rotas filhas e módulos.

Rotas filhas e organização

Conforme a aplicação cresce, organizar as rotas de forma clara torna-se um desafio prático. Não queremos um único arquivo com dezenas de rotas misturadas, nem componentes tentando lidar com muitas responsabilidades. É aqui que entram as rotas filhas (rotas nested) e a divisão por módulos. Esses conceitos ajudam a estruturar a aplicação em partes menores, facilitando a manutenção e deixando explícita a hierarquia de navegação.
Rotas filhas são rotas definidas dentro de outra rota "pai". Ou seja, uma rota pode ter a propriedade children com um array de sub-rotas. Na prática, isso significa que uma determinada rota pai terá um <router-outlet> em seu template, onde os componentes das rotas filhas serão renderizados. Por exemplo, podemos ter uma seção de "Devs" que possui suas próprias rotas internas (lista, detalhe, criar novo dev, etc.):
// Exemplo de rotas com children (dentro de um módulo DevsModule, por exemplo) const devsRoutes: Routes = [ { path: 'devs', component: DevsComponent, children: [ { path: '', component: DevListComponent }, // rota filha padrão (lista de devs) { path: 'novo', component: DevFormComponent }, // rota filha para criar novo dev { path: ':id', component: DevDetailComponent } // rota filha para detalhe do dev ]} ];
No cenário acima, temos a rota principal 'devs' rendendo um DevsComponent (poderia ser um componente que exibe um layout comum para a seção, com menu específico, por exemplo). Dentro dele, usamos <router-outlet> para carregar as rotas filhas: a lista de devs (path: '' vazio como default), o formulário de novo dev, ou o detalhe de um dev específico. Assim, a URL /devs/novo carrega o DevFormComponent dentro do layout de DevsComponent, enquanto /devs/256 carrega DevDetailComponent no mesmo espaço. Isso organiza melhor a aplicação, pois a seção "Devs" fica encapsulada.
Do ponto de vista de código, é recomendado separar as rotas de cada parte da aplicação em módulos de recurso (feature modules). Por exemplo, podemos ter um DevsModule que declara DevListComponent, DevDetailComponent, etc., e define suas rotas próprias via RouterModule.forChild(devsRoutes). O módulo raiz (AppModule) então importa esse DevsModule ou configura uma rota para carregá-lo (via lazy loading, assunto para o próximo artigo!). Essa abordagem modular segue boas práticas de arquitetura e mantém o projeto escalável. Organizar as rotas em módulos e sub-rotas facilita a colaboração entre times e deixa claro onde cada funcionalidade reside dentro do projeto (dica: isso está alinhado com conceitos do material Metodologias e Processos de Desenvolvimento da Rocketseat, que fala sobre organização de projetos e fluxos de trabalho eficientes).
👉
Quer entender processos que deixam sua base mais sólida? Dá uma olhada em Metodologias e Processos de Desenvolvimento.
Com a base de rotas estruturada e organizada, falta discutir como podemos proteger certas rotas para que apenas usuários autorizados acessem. Aí entram os guards de rota.

Guards de rota

Até agora, qualquer usuário navegando em nossa aplicação poderia acessar todas as rotas definidas. Mas em muitos casos, queremos restringir ou condicionar o acesso a certas rotas, por exemplo, uma área de admin que só deve ser acessível se o usuário estiver logado, ou uma página que pede algum pré-requisito antes de entrar. Para esses cenários, o Angular fornece os route guards (guardas de rota).
Um guard de rota é basicamente uma função (ou classe) que o Router chama antes de ativar uma rota, para decidir se aquela navegação pode continuar ou não. O tipo mais comum é o CanActivate, que responde à pergunta "o usuário pode ativar (acessar) esta rota?". Se o guard retornar true, a navegação prossegue; se retornar false, o Router cancela a navegação (podemos até redirecionar para outra página, como login). Em outras palavras, o guard é um porteiro da rota: só deixa passar quem cumprir certa condição.

canActivate para áreas protegidas

Vamos focar no guard CanActivate, usado geralmente para autenticação/autorização. Primeiro, precisamos criar um guard. Podemos usar o Angular CLI (ng generate guard auth) ou criar manualmente uma classe que implemente a interface CanActivate. Essa classe deve ter um método canActivate que retorne um boolean (ou Promise/Observable de boolean). Também costuma-se marcar o guard com @Injectable para poder injetar serviços nele, como um AuthService.
Por exemplo, um guard de autenticação simples poderia ser:
import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { // Lógica de autenticação simples: const usuarioLogado = false; // isso viria de um AuthService real, aqui é só exemplo if (!usuarioLogado) { // Se não está logado, redireciona para a página de login antes de bloquear this.router.navigate(['/login']); return false; } // Se estiver logado, permite acessar a rota return true; } }
No código acima, AuthGuard verifica uma variável usuarioLogado. Se o usuário não estiver logado (false), usamos o Router para navegar até /login e retornamos false para cancelar a rota atual. Se estiver logado, retornamos true e a navegação continua para o componente desejado. Em aplicações reais, obviamente essa lógica seria conectada a algum serviço de autenticação que indica o status do usuário.
Para que esse guard funcione, precisamos associá-lo à rota protegida. Fazemos isso adicionando a propriedade canActivate na definição da rota, apontando para o guard. Por exemplo, se quisermos proteger uma rota de admin:
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard] }
Agora, sempre que alguém tentar navegar para /admin, o Angular vai primeiro executar o nosso AuthGuard. Só se o canActivate retornar true é que o AdminComponent será carregado. Podemos ter múltiplos guards na mesma rota (o array em canActivate), e todos precisam retornar true para prosseguir.
Os guards não se limitam a autenticação. Podemos usar CanActivate para checar permissões (ex: authService.hasRole('ADMIN')), estado de pagamento, aceite de termos, etc. Existe também o guard CanActivateChild (para proteger todas as rotas filhas de um módulo de uma vez), CanDeactivate (para impedir sair de uma página, útil em formulários não salvos) e CanMatch (para decidir dinamicamente qual módulo de rota carregar). Mas a ideia central é a mesma: controle de acesso e navegação condicional.
Lembre-se apenas que os guards rodam no front-end e podem ser burlados inspecionando o código; então, nunca confie apenas neles para segurança de dados sensíveis, sempre tenha verificações no back-end também.
Com guards, fechamos as principais bases do roteamento Angular: definimos rotas (inclusive filhas), navegamos entre elas via links e código, e protegemos as rotas sensíveis. Isso tudo deixa a aplicação bem estruturada e a experiência do usuário muito mais consistente.

Conclusão e próximos passos

Você viu como o roteamento no Angular permite estruturar a aplicação em múltiplas páginas, manter a navegação fluida e garantir segurança básica nas rotas. Configuramos o RouterModule, criamos rotas básicas e rotas filhas, usamos routerLink e navegação programática, e implementamos um guard de autenticação simples. Essa base de roteamento organiza a aplicação e impulsiona a experiência tanto de quem desenvolve quanto de quem utiliza o produto.
E a jornada não para por aqui!
No próximo conteúdo da nossa série, vamos explorar Lazy Loading, uma técnica para carregar módulos de forma preguiçosa (sob demanda) e, assim, melhorar o desempenho da aplicação. Isso complementa o que aprendemos sobre roteamento, permitindo que partes do app sejam carregadas somente quando necessário. Se liga, porque no próximo artigo veremos na prática como o lazy loading pode deixar sua aplicação Angular ainda mais rápida e eficiente.
💜
Nos vemos em breve no próximo capítulo, bora codar!

A hora de investir em você é AGORA!

Um único investimento. Tudo que você precisa para evoluir na carreira!

Artigos_

Explore conteúdos relacionados

Descubra mais artigos que complementam seu aprendizado e expandem seu conhecimento.