Page 1
Angular Routing
As aplicações Web tradicionais usam server-side routing: colocamos um URL num browser,
que faz um pedido ao servidor; o servidor recebe o pedido, encaminha-o para um controlador
(routes to a controller), o controlador executa uma ação específica e cria uma vista que envia
como resposta para o browser.
Angular usa client-side routing muito semelhante no conceito, mas diferente na
implementação. No routing client-side não é feito um pedido ao servidor sempre que o URL
muda. As aplicações Angular são “Single Page Apps” (SPA) porque o servidor só transfere uma
única página. Uma aplicação Angular é constituída por um conjunto de componentes e Angular
através do routing (executando código JavaScript) mostra diferentes vistas (páginas) para
diferentes áreas ou atividades, apresentando componentes diferentes.
Como a aplicação é client-side, tecnicamente não seria necessário mudar o URL quando
mudámos de página. Mas se usássemos o mesmo URL para todas as páginas não seria possível
refrescar a página e manter a localização dentro da aplicação, não seria possível criar um
bookmark da página nem partilhar o URL.
Routing permite-nos definir diferentes strings URL, uma para cada área ou atividade da
aplicação.
Angular usa o modo de routing HTML5. Em HTML5 é possível criar programaticamente novas
entradas da história do browser que mudam o URL mostrado, sem fazer um novo pedido ao
servidor. Isto é possível através do método history.pushState.
Uma boa prática em Angular é criar um módulo top-level, separado, dedicado ao routing e
importado pelo módulo raiz AppModule.
Assim, para adicionar Routing a uma aplicação Angular já existente, devemos usar o seguinte
comando ng:
> ng generate module app-routing --flat --module=app
e fazer as modificações indicadas no tutorial tour-of-heroes (toh).
É mais simples criar uma nova aplicação Angular com Routing usando o seguinte comando ng:
> ng new AngularRouting --routing
Neste caso não é necessário fazer qualquer modificação.
Ficheiro app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
Page 2
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Ficheiro app.component.html:
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
<img width="50" src="data:image/svg+xml;base64,PHN2ZyB4bW . . . ZnPg==">
Welcome to {{title}}
</h1>
</div>
<router-outlet></router-outlet>
Ficheiro app-routing.module.ts:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
No array routes vamos definir as rotas para a nossa aplicação e o método
RouterModule.forRoot(routes) configura essas rotas.
Quando mudamos de rota geralmente pretendemos manter uma parte do layout e só
substituir uma secção interior com o componente dessa rota. Para indicar o local onde
renderizar o conteúdo de cada rota usamos o elemento router-outlet.
1. Criação de Rotas
Vamos criar 3 componentes: “Area1”, “Area2” e “Area3”.
Na página template do componente raiz da aplicação (app.component.html) vamos colocar 3
links: Área1, Área2 e Área3.
Cada link muda a página para o template do respetivo componente.
> ng generate component Area1
> ng generate component Area2
> ng generate component Area3
Page 3
src/app/area1/area1.component.html: <h1> Área 1 works! </h1>
src/app/area2/area2.component.html: <h1> Área 2 works! </h1>
src/app/area3/area3.component.html: <h1> Área 3 works! </h1>
No ficheiro app-routing.module.ts definimos as rotas da aplicação.
Cada rota contém um path e um componente associado.
Ficheiro app-routing.module.ts:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Area1Component } from './area1/area1.component';
import { Area2Component } from './area2/area2.component';
import { Area3Component } from './area3/area3.component';
const routes: Routes = [
{ path: 'area1', component: Area1Component },
{ path: 'area2', component: Area2Component },
{ path: 'area3', component: Area3Component },
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Para Angular navegar para uma dada rota o uso de um link HTML, como:
<div><a href = "/area1">Área 1</a></div>
desencadeia o reload da página, o que não é pretendido em SPAs.
A diretiva RouterLink (atributo routerLink) permite criar links para rotas sem fazer o reload da
página.
A diretiva RouterOutlet (elemento router-outlet) indica onde o componente de cada rota será
renderizado.
Ficheiro app.component.html:
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
<img width="50" src="data:image/svg+xml;base64,PHN2ZyB4bW . . . ZnPg==">
Welcome to {{title}}
</h1>
</div>
<h3> AppComponent Router Links:
<a routerLink = "area1"> Área 1 </a>
<a routerLink = "area2"> Área 2 </a>
<a routerLink = "area3"> Área 3 </a>
</h3>
<h6> AppComponent Router Views (router-outlet): </h6>
<router-outlet></router-outlet>
Page 4
2. Definição de Rotas Children (Nested Routes)
Quando algumas rotas só são acessíveis e vistas dentro de outras rotas é apropriado criá-las
como rotas children.
Cada área da nossa aplicação pode ter os seus próprios componentes filho, que também têm o
seu próprio routre-outlet.
Vamos considerar que era apropriado executar certas atividades nas áreas seguintes:
• na Área1 as atividades: Atividade1, Atividade2 e Atividade3.
• na Área2 as atividades: Atividade1 e Atividade4.
• na Área3 as atividades: Atividade5 e Atividade2.
Criação dos componentes para as atividades:
> ng generate component Atividade1
> ng generate component Atividade2
> ng generate component Atividade3
> ng generate component Atividade4
> ng generate component Atividade5
src/app/atividade1/atividade1.component.html: <h1> Atividade 1 works! </h1>
src/app/atividade2/atividade2.component.html: <h1> Atividade 2 works! </h1>
src/app/atividade3/atividade3.component.html: <h1> Atividade 3 works! </h1>
src/app/atividade4/atividade4.component.html: <h1> Atividade 4 works! </h1>
src/app/atividade5/atividade5.component.html: <h1> Atividade 5 works! </h1>
Alteração do ficheiro app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Area1Component } from './area1/area1.component';
import { Area2Component } from './area2/area2.component';
import { Area3Component } from './area3/area3.component';
import { Atividade1Component } from './atividade1/atividade1.component';
import { Atividade2Component } from './atividade2/atividade2.component';
import { Atividade3Component } from './atividade3/atividade3.component';
import { Atividade4Component } from './atividade4/atividade4.component';
import { Atividade5Component } from './atividade5/atividade5.component';
const routes: Routes = [
{ path:'area1', component: Area1Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade2', component: Atividade2Component },
{ path:'atividade3', component: Atividade3Component },
] },
{ path:'area2', component: Area2Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade4', component: Atividade4Component },
] },
{ path:'area3', component: Area3Component,
Page 5
children: [
{ path:'atividade5', component: Atividade5Component },
{ path:'atividade2', component: Atividade2Component },
] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Alteração do ficheiro area1.component.html
<h3>Area1 Router Links:
<a routerLink = "atividade1"> Atividade 1 </a>
<a routerLink = "atividade2"> Atividade 2 </a>
<a routerLink = "atividade3"> Atividade 3 </a>
</h3>
<h1>
Área 1 works!
</h1>
<h6>Area1 Router Views (router-outlet):</h6>
<router-outlet></router-outlet>
É neste elemento router-outlet que serão renderizados os componentes associados às rotas
children: area1/atividade1, area1/atividade2, area1/atividade3.
Alteração do ficheiro area2.component.html
<h3>Area2 Router Links:
<a routerLink = "atividade1"> Atividade 1 </a>
<a routerLink = "atividade4"> Atividade 4 </a>
</h3>
<h1>
Área 2 works!
</h1>
<h6>Area2 Router Views (router-outlet):</h6>
<router-outlet></router-outlet>
Alteração do ficheiro area3.component.html
<h3>Area3 Router Links:
<a routerLink = "atividade5"> Atividade 5 </a>
<a routerLink = "atividade2"> Atividade 2 </a>
</h3>
<h1>
Área 3 works!
</h1>
<h6>Area3 Router Views (router-outlet):</h6>
<router-outlet></router-outlet>
Page 6
3. Route Parameters
Para navegar para um recurso específico, usando por exemplo o URL /produtos/5 temos de
usar route parameters.
Para especificar que uma rota recebe um parâmetro colocamos : em frente ao path do
segmento, como:
/rota/:param
Page 7
Para usar route parameters e obter o parâmetro para uma dada rota é necessário importar
ActivatedRoute, que tem informação acerca da rota ativa.
Criar os componentes Produtos e Produto:
> ng generate component Produtos
> ng generate component Produto
As alterações ao ficheiro app.module.ts são feitas automaticamente pelo scaffolding do
comando ng - acrescentados 2 imports e 2 declarations (ProdutosComponent e
ProdutoComponent).
Alterações ao ficheiro app-routing.module.ts:
import { ProdutosComponent } from './produtos/produtos.component';
import { ProdutoComponent } from './produto/produto.component';
const routes: Routes = [
{ path:'area1', component: Area1Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade2', component: Atividade2Component },
{ path:'atividade3', component: Atividade3Component },
]
},
{ path:'area2', component: Area2Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade4', component: Atividade4Component },
]
},
{ path:'area3', component: Area3Component,
children: [
{ path:'atividade5', component: Atividade5Component },
{ path:'atividade2', component: Atividade2Component },
]
},
{ path:'produtos', component: ProdutosComponent,
children: [
{ path:':id', component: ProdutoComponent },
]
},
];
Alterações ao ficheiro app.component.html:
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
<img width="50" src="data:image/svg+xml;base64,PHN2Zy . . . vc3ZnPg==">
Welcome to {{title}}
</h1>
</div>
<h3> AppComponent Router Links:
Page 8
<a routerLink = "area1"> Área 1 </a>
<a routerLink = "area2"> Área 2 </a>
<a routerLink = "area3"> Área 3 </a>
<a routerLink = "produtos"> Produtos </a>
</h3>
<h6> AppComponent Router Views (router-outlet): </h6>
<router-outlet></router-outlet>
Injetar Router no construtor de ProdutosComponent para poder navegar programaticamente
para um route invocando a função navigate. O objeto Router injetado é mantido num campo
privado.
Ficheiro produtos.component.ts:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-produtos',
templateUrl: './produtos.component.html',
styleUrls: ['./produtos.component.css']
})
export class ProdutosComponent implements OnInit {
constructor(private router: Router) { }
mostrarProduto(id: string): void {
this.router.navigate(['/produtos', id]);
}
ngOnInit() {
}
}
Ficheiro produtos.component.html:
<h3>Produtos Router Links:
Id do Produto: <input #varId size="6">
<button (click) = "mostrarProduto(varId.value)"> Mostrar Produto </button>
</h3>
<h6>Produtos Router Views (router-outlet):</h6>
<router-outlet></router-outlet>
varId é uma variável referência template.
Variáveis referência template (template reference variables) são variáveis que referenciam um
elemento DOM, uma instância de um componente ou uma diretiva. O âmbito de validade
(scope) de uma variável template é todo o template. São definidas escrevendo o sinal hash (#)
junto com o nome, como um atributo adicional ao elemento DOM.
varId é um objeto do tipo HTMLInputElement que representa o elemento DOM Input. O objeto
DOM Input tem uma propriedade “value”, do tipo string, com o valor entrado pelo utilizador.
Page 9
Injetar ActivatedRoute no construtor de ProdutoComponent para poder obter o parâmetro de
uma dada rota.
Ficheiro produto.component.ts:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-produto',
templateUrl: './produto.component.html',
styleUrls: ['./produto.component.css']
})
export class ProdutoComponent implements OnInit {
id: number;
constructor(private route:ActivatedRoute) {
this.id = +this.route.snapshot.paramMap.get('id');
}
ngOnInit() {
}
}
route.snapshot é uma imagem estática da rota após o componente ser criado.
paramMap é um dicionário com os valores dos parâmetros da rota extraídos do URL.
O operador JavaScript + converte string para number.
Ficheiro produto.component.html:
<h1>
Detalhes do Produto com Id = {{ id }}
</h1>
Executando a aplicação, http://localhost:4200
Alterar o URL do browser para http://localhost:4200/produtos/444
Page 10
Clicar no link Produtos, preencher o campo de texto e clicar em Mostrar Produto.
Ao premir o botão “Mostrar Produto” o URL do browser também é atualizado.
Mantendo a vista do “ProdutoComponent”, alterar o Id do Produto no campo de texto e
premir o botão “Mostrar Produto” não tem efeito. Este problema ocorre porque Angular
verifica que a navegação desencadeada é tratada pelo mesmo componente que já está visível
para o utilizador e por isso não cria uma nova instância deste componente (por motivos de
eficiência). Quando este componente foi criado, recebeu o objeto ActivatedRoute, usado por
Page 11
Angular para conter os detalhes da rota corrente, mas só foi usada a sua propriedade
snapshot, imagem estática da rota após o componente ser criado.
O método navigate() invocado por mostrarProduto() atualizou o objeto ActivatedRoute, e este
objeto tem propriedades que permitem partes interessadas receber notificações.
Alteração do ficheiro produto.component.ts:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-produto',
templateUrl: './produto.component.html',
styleUrls: ['./produto.component.css']
})
export class ProdutoComponent implements OnInit {
id: number;
constructor(private route: ActivatedRoute) {
// this.id = +this.route.snapshot.paramMap.get('id');
route.params.subscribe(parametros => {
this.id = +parametros['id'];
});
}
ngOnInit() {
}
}
Verificar que mantendo a vista do “ProdutoComponent”, ao alterar o Id do Produto no campo
de texto e premir o botão “Mostrar Produto” a vista do componente Produto é atualizada.
Page 12
4. Protected Route
Vamos considerar que pretendemos ter uma rota protegida, por exemplo só permitir a
aplicação ir para /produtos se o utilizador está autenticado.
O router deve ser notificado quando a rota protegida for ativada. Nessa altura o serviço de
autenticação deve ser invocado para determinar se o utilizador está autenticado.
Para verificar se um componente pode ser ativado adicionamos uma classe Guard à chave
canActivate na configuração do router.
Vamos criar um serviço muito simples para autenticação e autorização de recursos.
>ng generate service Auth
Ficheiro auth.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class AuthService {
constructor() { }
login(userName: string, password: string): boolean {
if (userName === 'user' && password === 'password') {
localStorage.setItem('userName', userName);
return true;
}
return false;
}
logout(): any {
localStorage.removeItem('userName');
}
getUser(): any {
return localStorage.getItem('userName');
}
isLoggedIn(): boolean {
return this.getUser() !== null;
}
}
Em HTML5 localStorage permite persistir informação no browser. A existência do item
“userName” em localStorage servirá como uma flag para indicar se existe um utilizador
autenticado.
Para poder injetar o serviço AuthService nos componentes que iremos criar temos de fornecer
(provide) este serviço no sistema de injeção de dependências.
Page 13
Alteração do ficheiro app.module.ts (providers):
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Area1Component } from './area1/area1.component';
import { Area2Component } from './area2/area2.component';
import { Area3Component } from './area3/area3.component';
import { Atividade1Component } from './atividade1/atividade1.component';
import { Atividade2Component } from './atividade2/atividade2.component';
import { Atividade3Component } from './atividade3/atividade3.component';
import { Atividade4Component } from './atividade4/atividade4.component';
import { Atividade5Component } from './atividade5/atividade5.component';
import { ProdutosComponent } from './produtos/produtos.component';
import { ProdutoComponent } from './produto/produto.component';
import { AuthService } from './auth.service';
@NgModule({
declarations: [
AppComponent,
Area1Component,
Area2Component,
Area3Component,
Atividade1Component,
Atividade2Component,
Atividade3Component,
Atividade4Component,
Atividade5Component,
ProdutosComponent,
ProdutoComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [AuthService],
bootstrap: [AppComponent]
})
export class AppModule { }
Criação do componente Login com um formulário.
>ng generate component Login
Ficheiro login.component.ts:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
message: string;
constructor(public authService: AuthService) {
this.message = '';
}
Page 14
ngOnInit() {
}
login(userName: string, password: string): boolean {
this.message = '';
if (!this.authService.login(userName, password)) {
this.message = 'Credenciais Incorretas.';
setTimeout(function() {
this.message = '';
}.bind(this), 2000);
}
return false;
}
logout(): boolean {
this.authService.logout();
return false;
}
}
A função login(userName, password) será invocada pelo formulário:
• Se o serviço de autenticação (AuthService) validar a autenticação, este serviço guarda o userName no localStorage.
• Se o serviço de autenticação não validar a autenticação, a mensagem “Credenciais Incorretas” é mostrada durante 2 segundos.
A função bind é usada para ligar (bind) o contexto corrente (this) à função, mesmo que seja executada mais tarde. As funções login e logout retornam false para o browser não recarregar a página.
Ficheiro login.component.html:
<h1>Login</h1>
<form *ngIf="!authService.getUser()">
<label for="username">User: (<em>user</em>)</label>
<input name="username" #username>
<label for="password">Password: (<em>password</em>)</label>
<input type="password" name="password" #password>
<button (click)="login(username.value, password.value)"> Submit </button>
</form>
<h3 class="erro" *ngIf="message"> {{ message }} </h3>
<div *ngIf="authService.getUser()">
Logged in as <b>{{ authService.getUser() }}</b>
<br/>
<button (click)="logout()">Log out</button>
</div>
Ficheiro login.component.css:
.erro {
color: red;
}
Page 15
Criação da rota “login” em app-routing.module.ts e do link “Login” em app.component.html
Alterações ao ficheiro app-routing.module.ts:
import { LoginComponent } from './login/login.component';
const routes: Routes = [
{ path:'area1', component: Area1Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade2', component: Atividade2Component },
{ path:'atividade3', component: Atividade3Component },
]
},
{ path:'area2', component: Area2Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade4', component: Atividade4Component },
]
},
{ path:'area3', component: Area3Component,
children: [
{ path:'atividade5', component: Atividade5Component },
{ path:'atividade2', component: Atividade2Component },
]
},
{ path:'login', component: LoginComponent },
{ path:'produtos', component: ProdutosComponent,
children: [
{ path:':id', component: ProdutoComponent },
]
},
];
Alterações ao ficheiro app.component.html:
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
<img width="50" src="data:image/svg+xml;base64,PHN2Zy . . . vc3ZnPg==">
Welcome to {{title}}
</h1>
</div>
<h3> AppComponent Router Links:
<a routerLink = "area1"> Área 1 </a>
<a routerLink = "area2"> Área 2 </a>
<a routerLink = "area3"> Área 3 </a>
<a routerLink = "produtos"> Produtos </a>
<a routerLink = "login"> Login </a>
</h3>
<h6> AppComponent Router Views (router-outlet): </h6>
<router-outlet></router-outlet>
Page 16
Executar a aplicação, http://localhost:4200
Pretendemos que o componente Produtos só possa ser acedido por utilizadores autenticados.
Temos que criar uma classe guarda que implemente canActivate.
Page 17
> ng generate guard Auth
Ficheiro auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from
'@angular/router';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
const isLoggedIn = this.authService.isLoggedIn();
console.log('canActivate', isLoggedIn);
return isLoggedIn;
}
}
Configuração do Router para usar esta guarda:
• Usar AuthGuard na configuração da rota “produtos”
• Incluir AuthGuard na lista de providers, para poder ser injetado
Alteração do ficheiro app-routing.module.ts:
import { AuthGuard } from './auth.guard';
const routes: Routes = [
{ path:'area1', component: Area1Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade2', component: Atividade2Component },
{ path:'atividade3', component: Atividade3Component },
]
},
{ path:'area2', component: Area2Component,
children: [
{ path:'atividade1', component: Atividade1Component },
{ path:'atividade4', component: Atividade4Component },
]
},
{ path:'area3', component: Area3Component,
children: [
{ path:'atividade5', component: Atividade5Component },
{ path:'atividade2', component: Atividade2Component },
]
},
{ path:'login', component: LoginComponent },
{ path:'produtos', component: ProdutosComponent,
canActivate: [AuthGuard],
children: [
{ path:':id', component: ProdutoComponent },
]
},
];
Page 18
Alteração do ficheiro app.module.ts (providers):
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Area1Component } from './area1/area1.component';
import { Area2Component } from './area2/area2.component';
import { Area3Component } from './area3/area3.component';
import { Atividade1Component } from './atividade1/atividade1.component';
import { Atividade2Component } from './atividade2/atividade2.component';
import { Atividade3Component } from './atividade3/atividade3.component';
import { Atividade4Component } from './atividade4/atividade4.component';
import { Atividade5Component } from './atividade5/atividade5.component';
import { ProdutosComponent } from './produtos/produtos.component';
import { ProdutoComponent } from './produto/produto.component';
import { AuthService } from './auth.service';
import { LoginComponent } from './login/login.component';
import { AuthGuard } from './auth.guard';
@NgModule({
declarations: [
AppComponent,
Area1Component,
Area2Component,
Area3Component,
Atividade1Component,
Atividade2Component,
Atividade3Component,
Atividade4Component,
Atividade5Component,
ProdutosComponent,
ProdutoComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [ AuthService, AuthGuard ],
bootstrap: [AppComponent]
})
export class AppModule { }
Executar a aplicação, http://localhost:4200
Page 19
Se o utilizador não estiver autenticado, clicar no link Produtos não tem efeito.
Após autenticação é possível aceder à área dos Produtos.