Guardas de Rotas em Angular
Guardas de rotas (guards) permitem restringir a navegação por determinadas rotas. Por exemplo, se o acesso a um recurso específico exigir autenticação ou outras condições, podemos conceder ou negar o acesso ao usuário com base nessas verificações. Ou seja, os guards protegem o acesso aos recursos.
canActivate
canActivate
é um dos tipos de guards que permite controlar o acesso a um recurso durante a navegação por rotas. O método canActivate()
precisa implementar uma função com a seguinte definição:
type CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
Ela recebe dois parâmetros: os objetos ActivatedRouteSnapshot
e RouterStateSnapshot
, que contêm informações sobre a solicitação. O ActivatedRouteSnapshot permite acessar várias informações da solicitação, incluindo parâmetros de rota e query strings. Por exemplo, se a rota tiver o parâmetro id
, podemos obtê-lo assim:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : boolean {
// obtendo o id
console.log(route.params["id"]);
// ...restante do código
}
O resultado do método normalmente retorna um valor booleano: true
ou false
. Para permitir o acesso ao recurso solicitado, a função deve retornar true
. Caso contrário, para cancelar ou negar o acesso, ela deve retornar false
.
Como exemplo, vamos definir o seguinte projeto da aplicação:
helloapp/ ├── src/ │ ├── app/ │ │ ├── about.component.ts │ │ ├── about.guard.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── home.component.ts │ ├── main.ts │ ├── index.html ├── angular.json ├── package-lock.json ├── package.json └── tsconfig.json
Vamos analisar os elementos do projeto. Na pasta src/app
, temos dois componentes como exemplo. No arquivo home.component.ts
, temos o seguinte componente HomeComponent
:
import { Component } from "@angular/core";
@Component({
selector: "home-app",
template: `<h2>Principal</h2>`,
})
export class HomeComponent {}
No arquivo about.component.ts
, definimos o seguinte componente AboutComponent
:
import { Component } from "@angular/core";
@Component({
selector: "about-app",
template: `<h2>Sobre o site</h2>`,
})
export class AboutComponent {}
Suponha que desejamos restringir o acesso ao componente AboutComponent
. Para isso, vamos adicionar um arquivo na pasta src/app
, chamado about.guard.ts
, com o seguinte código:
import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
export const aboutGuard = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
console.log(route.routeConfig?.path); // pode obter várias informações sobre as rotas, parâmetros, etc.
return confirm("Você tem certeza de que deseja continuar?");
};
Aqui, definimos a função aboutGuard
, que segue a definição de canActivate
. Para testar ambos os cenários, chamamos o método confirm()
, que exibe uma janela de diálogo para confirmação. Se o usuário cancelar, o método confirm()
retornará false
. Caso o usuário confirme, o retorno será true
.
Além disso, o caminho solicitado é exibido através da propriedade route.routeConfig.path
. Portanto, ao tomar a decisão de permitir ou não o redirecionamento do usuário, podemos explorar diversas informações.
Agora, vamos definir as referências para os componentes no arquivo app.component.ts
:
import { Component } from "@angular/core";
import { RouterOutlet, RouterLink } from "@angular/router";
@Component({
selector: "my-app",
standalone: true,
imports: [RouterOutlet, RouterLink],
styles: `a {padding: 3px;}`,
template: `<div>
<h1>Roteamento no Angular</h1>
<nav>
<a routerLink="">Principal</a>
<a routerLink="/about">Sobre o site</a>
</nav>
<router-outlet></router-outlet>
</div>`,
})
export class AppComponent {}
No arquivo app.config.ts
, definimos e configuramos as rotas para os componentes:
import { ApplicationConfig } from "@angular/core";
import { provideRouter, Routes } from "@angular/router";
import { HomeComponent } from "./home.component";
import { AboutComponent } from "./about.component";
import { aboutGuard } from "./about.guard";
// definição das rotas
const appRoutes: Routes = [
{ path: "", component: HomeComponent },
{ path: "about", component: AboutComponent, canActivate: [aboutGuard] },
];
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes)],
};
Para restringir o acesso à rota /about
, adicionamos o parâmetro canActivate: [aboutGuard]
na definição da rota. Como resultado, ao tentar acessar /about
, será exibida uma janela de confirmação.
Injeção de Serviços
Freqüentemente, a decisão de permitir o acesso a um recurso depende de dados armazenados. Por exemplo, podemos definir um novo arquivo auth.service.ts
na pasta src/app
:
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root", // serviço global
})
export class AuthService {
isLoggedIn = true;
login(): void {
this.isLoggedIn = true;
}
logout(): void {
this.isLoggedIn = false;
}
}
Aqui, definimos a classe AuthService
como um serviço global. A variável isLoggedIn
indica se o usuário está autenticado. Para fins de demonstração, consideramos que o usuário está logado.
Agora, vamos modificar o arquivo about.guard.ts
para decidir a navegação com base na autenticação do usuário:
import { inject } from "@angular/core";
import { AuthService } from "./auth.service";
export const aboutGuard = () => {
const authService = inject(AuthService); // obtemos o serviço
return authService.isLoggedIn;
};
Essa é a mesma função aboutGuard
, apenas sem os parâmetros, já que não são necessários neste caso. Dentro da função, usamos inject()
para obter o serviço global AuthService
e retornamos o valor da variável isLoggedIn
.
canDeactivate
canDeactivate
também permite controlar a navegação, mas de uma maneira diferente. Ele é útil em cenários onde o usuário está inserindo dados, mas ainda não os salvou, e decide sair da página. Nesse caso, podemos exibir um aviso ou uma janela de confirmação para evitar a perda de informações inseridas.
Comparando com canActivate
, o canActivate
verifica se o usuário pode acessar um componente específico, enquanto o canDeactivate
verifica se ele pode sair de um determinado componente. A função canDeactivate
tem a seguinte definição:
type CanDeactivateFn<T> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
Vamos supor que queremos controlar a navegação a partir do componente AboutComponent
. Alteramos o código dele da seguinte maneira:
import { Component } from "@angular/core";
import { RouterLink } from "@angular/router";
@Component({
selector: "about-app",
standalone: true,
imports: [RouterLink],
template: `<h2>Sobre o site</h2>
<button (click)="save()">Salvar</button>`,
})
export class AboutComponent {
saved: boolean = false;
save() {
this.saved = true;
}
}
Para simular a funcionalidade, adicionamos a propriedade saved
à classe AboutComponent
, que indica se os dados foram salvos. Usando o método save()
, acionado pelo clique de um botão, podemos controlar o valor dessa variável. Por exemplo, se o botão foi pressionado, significa que os dados foram salvos, e o valor de saved
será true
.
Para controlar a navegação a partir do AboutComponent
, adicionamos o arquivo exit.about.guard.ts
na pasta src/app
com o seguinte código:
import { AboutComponent } from "./about.component";
export const exitAboutGuard = (component: AboutComponent) => {
if (!component.saved) {
return confirm("Você deseja sair da página?");
}
return true;
};
Na função exitAboutGuard
, o parâmetro component recebe o tipo do componente de onde ocorre a navegação: neste caso, o AboutComponent
. Dessa forma, podemos considerar o estado do componente ao realizar a navegação, especificamente a variável saved
. Se this.saved
for false
(indicando que os dados não foram salvos), exibimos uma janela de confirmação.
Se a função retornar true
, a navegação será permitida. Se retornar false
, o usuário permanecerá na página.
Para ativar o exitAboutGuard
, alteramos a rota para o AboutComponent
no arquivo app.config.ts
:
import { ApplicationConfig } from "@angular/core";
import { provideRouter, Routes } from "@angular/router";
import { HomeComponent } from "./home.component";
import { AboutComponent } from "./about.component";
import { exitAboutGuard } from "./exit.about.guard";
// definição das rotas
const appRoutes: Routes = [
{ path: "", component: HomeComponent },
{ path: "about", component: AboutComponent, canDeactivate: [exitAboutGuard] },
];
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes)],
};
Aqui, no campo da rota, incluímos o parâmetro:
canDeactivate: [exitAboutGuard];
Agora, ao tentar sair do componente AboutComponent
(se saved
for igual a false
), será exibida uma janela de confirmação perguntando ao usuário se ele realmente deseja sair da página.