Rotas
[info] Objetivos do capítulo:
- apresentar o conceito de rotas
- demonstrar a utilização de rotas para apresentação de componentes
- demonstrar a diferença da estrutura de software desse capítulo para os capítulos anteriores
- demonstrar como o Angular implementa e utiliza o conceito de rotas
- demonstrar a utilização de parâmetros de rota
Branch: livro-rotas
Os capítulos anteriores demonstraram como trabalhar com elementos principais da arquitetura do Angular, principalmente componentes e serviços. Esses elementos têm o objetivo de compor a arquitetura de um software desenvolvido em Angular e, como visto, há vários recursos que permitem a interação entre eles.
Este capítulo apresenta outro recurso de arquitetura de software desenvolvido em Angular que, na verdade, aumenta a usabilidade do software.
Antes de apresentar a parte técnica é importante lidar com alguns conceitos.
URI, URL e URN
URI (Uniform Resource Identifier) é um identificador de um recurso. A sintaxe desse identificador é:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
Onde os elementos entre [] são opcionais e:
- scheme: representa o protocolo ou o contexto;
- user e password: representam as credenciais do usuário;
- host: representa o identificador do dispositivo que contém o recurso;
- port: representa o número da porta do dispositivo;
- path: representa o caminho do recurso;
- query: uma cadeia de caracteres que representa parâmetros de URL, um conjunto de pares
chave=valor
separados por&
; e - fragment: uma cadeia de caracteres com formato dependente do contexto.
A figura a seguir ilustra um URI e a sua composição:
O URI identifica um recurso disponível na internet, mais especificamente um repositório do Github. Provavelmente você já conhece isso e pode ser que use os termos endereço e URL. Você não está errado. URL é a forma mais comum de URI na internet. URL (Uniform Resource Locator) é um endereço de um recurso na internet. Além disso, URL é a forma mais comum de criar _links _entre páginas web e incorporar uma imagem em uma página web, por exemplo.
URN (Uniform Resource Name) é o nome de um recurso em um contexto específico. Por exemplo: o ISBN (International Standard Book Number) de uma edição de "Romeu e Julieta", de William Shakespeare, é 0-486-27557-4; seu URN poderia ser urn:isbn:0-486-2557-4.
Voltando para URL e o contexto da internet, os browsers geralmente fornecem uma barra de endereço, por meio da qual o usuário indica qual URL deseja acessar, por exemplo a URL de uma página web. A partir do momento que o browser acessa uma página web ele passa a armazenar o primeiro endereço acessado e os demais endereços que forem acessados por meio de cliques em links.
Esse é, provavelmente, o formato mais intuitivo e o mais utilizado para acessar páginas web. Justamente por isso é necessário repensar a forma como o aplicativo desenvolvido em Angular não apenas entrega conteúdo para o usuário mas também como permite que o usuário o acesse.
Rotas
Uma rota está diretamente relacionada a URL, ou seja, também funciona como um localizador de um recurso. A diferença é que acrescenta a possibilidade de utilizar parâmetros de rota. Por exemplo: considere um site de notícias noticias.to que permite acessar a notícia "Governo paga salarios de servidores", cujo identificador é 7899, está na categoria "política" e foi publicada em 20/12/2017, por meio do URL:
https://noticias.to/noticias/politica/2017/12/20/governo-paga-salarios-de-servidores/7899
Há informações no URL que pertencem à notícia e mudam de uma notíca para outra:
- categoria: politica
- ano: 2017
- mês: 12
- dia: 20
- titulo: governo-paga-salarios-de-servidores
- identificador: 7899
Analisando URLs de outras notícias alguém poderia chegar à conclusão de que há um padrão:
/noticias/categoria/ano/mes/dia/titulo/identificador
Independentemente de possuir parâmetros de rota, uma rota é um padrão. Cada uma dessas informações (categoria, ano, mes, dia, titulo, identificador), que muda de uma notícia para outra, pode ser representada como um parâmetro de rota.
A implementação desse conceito pode variar entre frameworks, mas provavelmente as mesmas funcionalidades estão disponíveis:
- definir uma rota (e, opcionalmente, usar parâmetros de rota)
- identificar valores dos parâmetros de rota
Além disso, como URLs são localizadores de recursos, rotas também servem para esse propósito, ou seja, uma rota está associada algum recurso e é uma forma de acessá-lo.
Estrutura do software
Aqui fazemos uma pequena pausa no assunto de rotas para entender a estrutura do software construído nesse capítulo, ilustrada pela figura a seguir.
Como mostra a figura a estrutura do software contém:
- um módulo:
AppModule
- quatro componentes:
AppComponent
,HomeComponent
,ListaDeDisciplinasComponent
eEditorDeDisciplinasComponent
- um serviço:
DisciplinasService
AppComponent
é utilizado como componente do bootstrap do módulo AppModule
.
HomeComponent
é o primeiro componente visualizado pelo usuário, representando a tela inicial do aplicativo.
ListaDeDisciplinasComponent
apresenta a lista de disciplinas e também permite que o usuário exclua uma disciplina e acesse a tela de cadastro e edição de uma disciplina.
EditorDeDisciplinaComponent
funciona como um editor de disciplinas, servindo tanto para cadastrar quanto para editar uma disciplina.
Até o capítulo anterior também havia uma estrutura de classes semelhante a essa. Entretanto, o AppComponent
continha a lógica de gerenciamento dos dados e incluía os componentes ListaDeDisciplinasComponent
e EditorDeDisciplinaComponent
, utilizando input e output para entregar e receber dados deles. Aqui neste capítulo, com o recurso de rotas, o resultado é diferente:
AppComponent
não tem a lógica para gerenciar dados de disciplinasListaDeDisciplinasComponent
não tem input ou output, pois interage diretamente com oDisciplinasService
EditorDeDisciplinaComponent
não tem input ou ouptut, interage diretamente com oDisciplinasService
e usa o recurso parâmetros de rota para saber quando é necessário editar uma disciplina (e qual deve ser editada) ou cadastrar uma disciplina.
Rotas no Angular
Controlar a visibilidade de componentes é uma tarefa simples. Isso pode ser consideguido, por exemplo, utilizando as diretivas ngIf
e hidden
. Entretanto, a complexidade aumenta na proporção da quantidade de componentes ou da complexidade das interações com o usuário. Por isso o Angular implementa o conceito de rotas de uma maneira bastante útil.
Para usar rotas é possível definí-las das seguintes formas:
- no root module
- no feature module
- no módulo de rotas
Nesse momento vamos usar a primeira abordagem. Modifique o arquivo ./src/app/app.module.ts
:
...
import {RouterModule, Routes} from '@angular/router';
... (imports dos componentes)
const appRoutes: Routes = [
{path: 'disciplinas', component: ListaDeDisciplinasComponent},
{path: 'disciplinas/:id', component: EditorDeDisciplinaComponent},
{path: '', component: HomeComponent,},
{path: '**', component: PaginaNaoEncontradaComponent}
];
@NgModule({
declarations: [
AppComponent,
ListaDeDisciplinasComponent,
EditorDeDisciplinaComponent,
HomeComponent,
PaginaNaoEncontradaComponent
],
imports: [
RouterModule.forRoot(
appRoutes,
{enableTracing: true}
),
...
],
providers: [DisciplinasService],
bootstrap: [AppComponent]
})
export class AppModule {
}
A primeira coisa a destacar é a variável appRoutes
, do tipo Routes
. O pacote @angular/router
fornece o módulo RouterModule
e a classe Routes
. Na prática a variável appRoutes
recebe um array no qual cada item é um objeto com dois atributos:
path
: representa a rota para o componente; ecomponent
: representa o componente associado à rota.
A primeira rota, disciplinas
, está associada ao componente ListaDeDisciplinasComponent
.
A segunda rota, disciplinas/:id
, está usando um parâmetro de rota. A sintaxe do Angular para parâmetro de rotas é usar o sinal de dois pontos seguido do nome do parâmetro. Nesse caso o parâmetro chama-se id
. A rota está associada ao componente EditorDeDisciplinaComponent
.
A terceira e a quarta rotas têm um comportamento especial. A terceira rota é uma string vazia e está associada ao componente HomeComponent
. Isso significa que essa é a rota padrão. A quarta rota é **
, associada ao componente PaginaNaoEncontradaComponent
. Isso significa um atalho para o seguinte comportamento: se o usuário fornecer uma rota não definida, leve até o componente PaginaNaoEncontradaComponent
.
Por fim, as rotas são inseridas no módulo por meio de um elemento do array imports
resultante de uma chamada para o método RouterModule.forRoot()
, passando como parâmetro o array de rotas, o a variável appRoutes
.
Quando o software é executado o Angular busca uma combinação entre a URL fornecida no browser e as rotas definidas no módulo. Isso é feito de cima para baixo, procurando no array appRoutes
. Ao encontrar uma rota correspondente, o Angular cria uma instância do componente e a apresenta. A figura a seguir ilustra esse processo.
O modo de comparação entre a URL no browser e o caminho da rota é muito interessante. A forma de comparação considera se o caminho da rota combina com o final da URL do browser. A figura ilustra a busca por uma rota correspondente à URL http://localhost:4200
e interrompe o processo quando encontra a primeira correspondência. Nesse mesmo sentido se a URL no browser for http://localhost:4200/disciplinas
o Angular encontra a rota disciplinas
. E assim por diante.
Voltando à figura a URL indica o que é conhecido como raiz do site. É opcional que a URL termine com /
.
Outra característica importante desse processo é a ordem das rotas. Como disse, o Angular procura pela rota que combina com o final da URL, assim, se a rota padrão estiver no início da lista das rotas, então o Angular sempre a encontrará. Da mesma forma, se não encontrar uma rota correspondente o Angular vai até a rota **
.
Shell component
Quando o Angular identifica a rota e o componente associado a ela é necessário apresentá-lo (lembre-se que o componente é, provavelmente, visual). Assim, ao utilizar rotas o Angular requer que o módulo (nesse caso o AppModule
) utilize um componente como shell, o que significa que o Template do componente precisa indicar onde outros componentes serão apresentados. Para isso o Angular utiliza duas informações:
- qual o componente o módulo declara como
bootstrap
(oAppComponent
é o shell component) - onde está o elemento
router-outlet
no Template doAppComponent
A figura a seguir ilustra esse processo, considerando que a URL no browser é http://localhost:4200
.
Como mostra a figura, o Angular combina o Template do AppComponent
com o Template do HomeComponent
para gerar uma saída HTML [unificada] para o browser. Importante perceber que tudo o que está antes e depois do elemento router-outlet
é mantido na saída. Por isso o Angular usa, em conjunto, a descoberta da rota e do componente associado e o conteúdo do shell component.
Parâmetros de rota
No caso do EditorDeDisciplinaComponent
há uma lógica no funcionamento do componente que depende de identificar, conforme a rota, se é necessário editar uma disciplina existente (e qual é essa disciplina) ou se é necessário cadastrar uma disciplina. Obviamente, nesse caso, o componente executa duas funções (cadastrar e editar disciplina). Uma alternativa é utilizar dois componentes, cada qual com uma função diferente.
A rota disciplinas/:id
, como já visto, possui um parâmetro de rota chamado id
. Obter o valor desse identificador da disciplina na URL é uma tarefa importante desse processo de lidar com rotas no Angular. Para fazer isso, o componente EditorDeDisciplinaComponent
possui o atributo route
, uma instância de ActivatedRoute
, como mostra o trecho de código a seguir.
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
...
export class EditorDeDisciplinaComponent implements OnInit {
...
constructor(private route: ActivatedRoute,
private router: Router,
private disciplinasService: DisciplinasService) {
}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
if (id == 'cadastrar') {
this.disciplina = new Disciplina(null, null, null);
this.status = null;
} else {
this.disciplinasService.encontrar(parseInt(id))
.subscribe(...);
}
}
...
}
Como o componente implementa a interface OnInit
, o código do método ngOnInit()
é responsável por:
- identificar o valor do parâmetro de rota: isso é feito por meio de uma chamada para o método
route.snapshot.paramMap.get()
. O parâmetro para o métodoget()
é o nome do parâmetro de rota desejado - tratar o valor do parâmetro de rota: o valor retornado por
get()
é do tipostring
. Se o valor do parâmetro de rota for igual a'cadastrar'
então o componente opera no modo de cadastro de disciplina; caso contrário, o código faz uma chamada ao métododisciplinasService.encontrar()
, passando como parâmetro o valor do parâmetro de rota convertido para o tiponumber
, usando a funçãoparseInt()
, então o componente entra no modo de edição de uma disciplina.
O método ngOnInit()
é executado pelo Angular no início do processo de instanciar o componente e apresentá-lo na tela.
Navegação
A navegação é o recurso que permite mudar de uma rota para outra. Isso pode ser feito por meio de uma ação do usuário (por exemplo, quando ele clica em um link) ou via código.
A navegação por meio de link utiliza o elemento a
e o atributo routerLink
, como mostra o Template do HomeComponent
:
<h2>Gerenciador de dados escolares</h2>
<p>
<a routerLink="/disciplinas" class="btn btn-primary">
Gerenciar disciplinas
</a>
</p>
O atributo routerLink
tem o valor /disciplinas
, o que significa que quando o usuário clicar no link ocorrerá uma navegação para a rota disciplinas
. É importante destacar que, embora a definição da rota não inclua uma barra no início do caminho, o mesmo não acontece aqui, com o link. É como determinar que o link sempre considera a raiz do site (usando um caminho absoluto).
Na lista de disciplinas há um botão que permite editar uma disciplina. No clique do botão há uma chamada para a função editar()
, cujo trecho de código é apresentado a seguir.
...
export class ListaDeDisciplinasComponent implements OnInit {
...
constructor(private disciplinasService: DisciplinasService,
private router: Router) {
}
editar(disciplina) {
this.router.navigate(['disciplinas', disciplina.id]);
}
}
O ListaDeDisciplinasComponent
possui o atributo router
, do tipo Router
. A classe Router
fornece o método navigate()
, que é utilizado para fazer a navegação via código. O parâmetro para o método navigate()
é um array cujos elementos representam partes de uma URL. No caso do método editar()
os parâmetros são: 'disciplinas'
e disciplina.id
. O Angular utiliza esses elementos para gerar, por exemplo, a URL /disciplinas/1
para editar a disciplina com identificador 1.
Padrão de trabalho (workflow)
Lidar com rotas no Angular é um processo simples, mas que aumenta de complexidade na proporção da quantidade de componentes, módulos ou na complexidade da arquitetura do software. Entretanto, lidar com esse processo contém alguns passos padrão (supondo que o projeto já tenha iniciado e adote a mesma estrutura do Angular CLI):
- criar componente: a primeira coisa a fazer é criar o componente desejado. Como é importante conduzir esse processo de forma iterativa, não se preocupe em construir o componente por inteiro, deixe-o como criado pelo Angular CLI;
- definir rotas: depois, defina as rotas (utilizando, por exemplo, o root module, como visto neste capítulo);
- implementar a lógica de negócio em um serviço: ao utilizar serviços, implemente a lógica de negócio do serviço; e
- implementar lógica do componente e usar o serviço: por fim, melhore a lógica do componente, fornecendo código para a lógica da interface e da lógica de negócio, e utilize o serviço.
Adotar esse padrão de trabalho pode tornar o desenvolvimento mais simples e rápido, bem como reduzir a quantidade de erros e permitir a identificação dos erros de forma mais ágil.
[info] Resumo do capítulo
- Rotas servem como recurso para integrar componentes ao mecanismo de navegação do browser, que já é conhecido pelos usuários
- É possível identificar valores dos parâmetros de rota utilizando a classe
ActivatedRoute
- É possível fazer navegação entre as rotas usando o atributo
routerLink
e a classeRouter
- O shell component é um componente utilizado para fornecer uma estrutura padrão para os componentes de um módulo
- O shell component utiliza o elemento
router-outlet
- O Angular combina o Template do shell component e do componente associado a uma rota para gerar uma saída comum para o browser