Prévia do material em texto
1.1
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
2.11
3.1
3.2
3.3
3.4
3.5
3.6
Tabela de conteúdos
Introdução
Conceitos iniciais
Iniciando com o Angular
Angular CLI
Software de gerenciamento escolar
Criando o projeto
Depurando código no browser
Iniciando com teste de software
Apresentando a lista de disciplinas
Cadastrando uma disciplina
Excluindo uma disciplina
Editando dados de uma disciplina
Depurando código no browser
Nível intermediário
Interação entre componentes
Serviços
Acessando dados externos de forma assíncrona
Acessando dados de uma API HTTP
Rotas
Feature-modules
1
Desenvolvimento web front-end com
Angular
Este é um livro open-source, tanto no conteúdo quanto no código-fonte associado. O
conteúdo é resultado de algumas práticas com o Framework Angular e segue uma
abordagem prática, com foco no entendimento de conceitos e tecnologias no contexto do
desenvolvimento de um software de gerenciamento escolar.
Com o passar dos capítulos será possível perceber a utilização iterativa e incremental de
recursos até chegar à conclusão do desenvolvimento do gerenciador escolar.
Parte do conteúdo do livro é baseada na documentação oficial do Angular, disponível em
https://angular.io.
Como o Angular é um projeto em constante desenvolvimento (pelo menos até agora) serão
publicadas atualizações no conteúdo do livro sempre que possível, para refletir novos
recursos e funcionalidades. No momento, o conteúdo do livro é baseado na versão 5.2.0.
Conhecimentos desejáveis
Este livro aborda o desenvolvimento de software front-end para a web do ponto-de-vista do
Angular. Isso quer dizer que não trata de conceitos iniciais de HTML, CSS, JavaScript,
TypeScript e Bootstrap. Entretanto, os conceitos fundamentais dessas tecnologias vão
sendo apresentados no decorrer dos capítulos, conforme surge a necessidade deles.
Para aprender mais sobre essas tecnologias recomendo essas fontes:
TypeScript: Documentação oficial do TypeScript - Microsoft, TypeScript Deep Dive
HTML, CSS e JavaScript: W3Schools
Boostrap: Documentação oficial do Bootstrap
Código-fonte
O software Angular Escola, que serve como apoio para este livro está disponível
gratuitamente no Github: https://github.com/jacksongomesbr/angular-escola. O repositório
provê branches que demonstram a evolução do desenvolvimento do software na mesma
ordem dos capítulos do livro. Cada capítulo, se necessário, indica qual branch utilizar.
Introdução
2
https://angular.io
https://www.typescriptlang.org/docs/home.html
https://www.gitbook.com/book/basarat/typescript/details
https://www.w3schools.com/
http://getbootstrap.com/docs/4.0/getting-started/introduction/
https://github.com/jacksongomesbr/angular-escola
Dependências
Para utilizar o código-fonte do livro e desenvolver em Angular você precisa, antes, montar
seu ambiente de desenvolvimento com algumas ferramentas:
1. Git: disponível em https://git-scm.com/
2. Node.js: disponível em https://nodejs.org representa um ambiente de execução do
JavaScript fora do browser e também inclui o npm, um gerenciador de pacotes
3. Editor de textos ou IDE: atualmente há muitas opções, mas destaco estas:
i. VisualStudioCode: disponível em https://code.visualstudio.com/
ii. Nodepad++: disponível em https://notepad-plus-plus.org/
iii. PHPStorm ou WebStorm: disponíveis em https://www.jetbrains.com
Visão geral
A tabela a seguir apresenta uma visão geral dos conceitos apresentados ou discutidos em
cada capítulo do livro.
Capítulo Conteúdos
Iniciando com o Angular Arquitetura do Angular; Elementos da Arquitetura doAngular (ex.: módulos e componentes)
Angular CLI Ferramenta Angular CLI; Comandos
Criando o projeto Ferramenta Angular CLI; Comando new
Apresentando a lista de
disciplinas Data binding; Diretivas; Eventos
Cadastrando uma disciplina Formulários; Two-way Data binding; Eventos
Excluindo uma disciplina Two-way Data Binding; Eventos
Editando dados de uma
disciplina Formulários; Two-way Data Binding; Eventos
Melhorando a interface gráfica
do CRUD de disciplinas
Formulários; Data Binding; Eventos; Bootstrap; Font
Awesome
Interação entre componentes Criação de componentes; Input e Output parainteração entre componentes
Introdução
3
https://git-scm.com/
https://nodejs.org
https://code.visualstudio.com/
https://notepad-plus-plus.org/
https://www.jetbrains.com
Iniciando com o Angular
[info] Objetivos do capítulo:
demonstrar os elementos da Arquitetura do Angular
demonstrar a relação entre os elementos da Arquitetura do Angular e arquivos do
projeto
Branch: iniciando
O Angular é um framework para o desenvolvimento de software front-end. Isso quer dizer
que utiliza tecnologias padrão do contexto web como HTML, CSS e uma linguagem de
programação como JavaScript ou TypeScript.
Um software desenvolvido em Angular é composto por diversos elementos como: módulos,
componentes, templates e serviços. Esses elementos fazem parte da arquitetura do
Angular, que é ilustrada pela figura a seguir.
Essa arquitetura de software orientada a componentes implementa conceitos de dois
padrões de arquitetura de software:
MVC (Model, View, Controller) é um padrão de software que separa a representação da
informação (Model) da interação do usuário com ele (View-Controller). Geralmente,
Model e Controller são representados por código em linguagem de programação
(classes e/ou funções) e View é representado por HTML e CSS.
MVVM (Model, View, View-Model) é um padrão de software semelhante ao MVC, com
a diferença de que o View-Model utiliza recurso de data binding (mais sobre isso
Iniciando com o Angular
4
https://github.com/jacksongomesbr/angular-escola/tree/iniciando
depois) para fazer com que a View seja atualizada automaticamente quando ocorrer
uma modificação no Model.
No contexto do Angular esses elementos são descritos conforme as seções a seguir.
Elementos da Arquitetura do Angular
Módulos
Módulos representam a forma principal de modularização de código. Isso significa que um
módulo é um elemento de mais alto nível da arquitetura do Angular e é composto por outros
elementos, como componentes e serviços.
Um software desenvolvido em Angular possui pelo menos um módulo, chamado root
module (módulo raiz). Os demais módulos são chamados feature modules (módulos de
funcionalidades).
Bibliotecas
Bibliotecas funcionam como um agrupador de elementos de software desenvolvido em
Angular. Bibliotecas oficiais têm o prefixo @angular . Geralmente é possível instalar
bibliotecas utilizando o npm (gerenciador de pacotes do NodeJs).
Uma biblioteca pode conter módulos, componentes, diretivas e serviços.
Componentes
Um componente está, geralmente, relacionado a algo visual, ou seja, uma tela ou parte
dela. Nesse sentido, um componente possui código (Controller) que determina ou controla
o comportamento da interação com o usuário (View ou Template).
O Template determina a parte visual do componente e é definido por código HTML e CSS,
além de recursos específicos do Angular, como outros componentes e diretivas.
Metadados
Metadados são um recurso do Angular para adicionar detalhes a classes. Isso é utilizado
para que o Angular interprete uma classe como um Módulo ou como um Componente, por
exemplo.
Data binding
Iniciando com o Angular
5
Data binding (que seria algo como "vinculação de dados" em português) é um reucrso do
Angular que representa um componente importante da sua arquitetura. Considere os
seguintes elementos:
um Model define dados que serão apresentados no Template
um Template apresenta os dados do Model
um Controller ou um View-Model determina o comportamento do Template
Se o Controller atualiza o Model, então o Template tem que ser atualizado
automaticamente. Se o usuário atualiza o Model por meio do Template (usando um
formulário, por exemplo) o Controller também precisa ter acesso ao Model atualizado. O
Data Binding atua garantindo que esse processo ocorra dessa forma.
Diretivas
Diretivas representam um conceito do Angular que é um pouco confuso. Na prática, um
Componenteé uma Diretiva com um Template. Assim, um Componente é um tipo de
Diretiva, que nem sempre está relacionada a algo visual. Angular define dois tipos de
diretivas:
Diretivas Estruturais: modificam o Template dinamicamente por meio de manipulação
do DOM (Document Object Model), adicionando ou removendo elementos HTML
Diretivas de Atributos: também modificam o Template, mas operam sobre elementos
HTML já existentes
Serviços
Um Serviço é uma abstração do Angular utilizado para isolar a lógica de negócio de
Componentes. Na prática, um Serviço é representado por uma classe com métodos que
podem ser utilizados em Componentes. Para isso, para que um Componente utilize um
serviço, o Angular utiliza o conceito de Injeção de Dependência (DI, do inglês
Dependency Injection). DI é um padrão de software que faz com que dependências sejam
fornecidas para quem precisar. Na prática, o Angular identifica as dependências de um
Componente e cria automaticamente instâncias delas, para que sejam utilizadas
posteriormente no Componente.
Enquanto esses elementos da Arquitetura do Angular representam conceitos, é importante
visualizar a relação deles com elementos práticos do software, ou seja, código. Para isso, a
seção a seguir apresenta a estrutura padrão de um software desenvolvido em Angular.
Iniciando com o Angular
6
Estrutura padrão de um software desenvolvido
em Angular
Um software desenvolvido em Angular é representado por vários arquivos HTML, CSS,
TypeScript e de configuração (geralmente arquivos em formato JSON).
+ src
+ app
- app.component.css
- app.component.html
- app.component.ts
- app.module.ts
+ assets
+ environments
- environment.prod.ts
- environment.ts
- index.html
- maint.ts
- polyfills.ts
- styles.css
- tsconfig.app.json
- typings.d.ts
- .angular-cli.json
- package.json
- tsconfig.json
- tslint.json
No diretório raiz do software:
src : contém o código-fonte do software (módulos, componentes etc.)
.angular-cli.json : contém configurações do projeto Angular (nome, scripts etc.)
package.json : contém configurações do projeto NodeJS (um projeto Angular utiliza
NodeJS para gerenciamento de pacotes e bibliotecas, por exemplo)
tsconfig.json e tslint.json : contêm configurações do processo de tradução de
código TypeScript para JavaScript
No diretório src :
app : contém o root module e os demais feature modules do projeto
assets : contém arquivos CSS, JSON e scripts, por exemplo
environments : contém arquivos de configuração do ambiente de desenvolvimento
( environment.ts ) e de produção ( environment.prod.ts )
index.html : contém o código HTML inicial para o projeto e inclui o componente
principal do root module
main.ts : contém o código TypeScript necessário para iniciar o software (processo
Iniciando com o Angular
7
chamado de Bootstrap)
polyfills.ts : contém código TypeScript que indica scripts adicionais a serem
carregados pelo Browser para funcionamento do software como um todo e para
questões de compatibilidade com versões antigas de Browsers
style.css : contém o código CSS para definir estilos globais para o software
tsconfig.app.json e typings.d.ts : complementam configurações do arquivo
../tsconfig.json específicas para o software em questão
No diretório app :
app.component.css , app.component.html e app.component.ts : definem o component
AppComponent, respectivamente: apresentação por meio de CSS, Template e
Controller. Basicamente, estes três arquivos formam a base de todo componente
app.module.ts : código TypeScript que define o root module
Esse capítulo incial do livro apresentou conceitos importantes do Angular. A maior parte dos
conceitos está diretamente relacionada a código-fonte (HTML, CSS, TypeScript e JSON)
que estão em arquivos do diretório onde encontra-se o projeto.
Sempre que necessário, volte a esse capítulo para revisar conceitos do Angular. Muito
provavelmente, mesmo desenvolvedores experientes precisem, de tempos em tempos,
rever essas definições da arquitetura do Angular.
Iniciando com o Angular
8
Angular CLI
[info] Objetivos do capítulo:
apresentar o Angular CLI
apresentar comandos para criação de projetos, módulos, componentes e outros
elementos
apresentar o arquivo .angular-cli.json
O Angular CLI (disponível em https://cli.angular.io) é uma ferramenta para inicializar,
desenvolver, criar conteúdo e manter software desenvolvido em Angular. A principal
utilização dessa ferramenta começa na criação de um projeto. Você pode fazer isso
manualmente, claro, mas lidar com todas as configurações necessárias para o seu software
Angular pode não ser algo fácil, mesmo se você for um desenvolvedor com nível de
conhecimento médio a avançado.
[info] cê-éle-í?
Como parte do vocabulário do Angular, geralmente é interessante pronunciar angular
cê-ele-i, isso mesmo, CLI é uma sigla para Command Line Interface (no português
Interface de Linha de Comando). Assim, usar "cli" não é exclusividade alguma do
Angular -- provavelmente você verá isso em outros projetos.
As seções a seguir vão apresentar como instalar e utilizar o Angular CLI para desenvolver
software Angular.
Instalando
O Angular CLI é distribuído como um pacote do NodeJS, então você não encontrará um
instalador, como acontece com software tradicional. Ao invés disso, você precisa de um
ambiente de desenvolvimento com o NodeJS instalado (esse capítulo não trata disso).
Depois de ter o NodeJS instalado, a instalação do Angular CLI passa pela utilização do
npm, o gerenciador de pacotes do NodeJS. Para isso, a seguinte linha de comando deve
ser executada:
npm install -g @angular/cli
Isso fará uma instalação global do Angular CLI (por isso o -g na linha de comando) e
disponibilizará o programa ng .
Angular CLI
9
https://cli.angular.io
[warning] Problemas na instalação?
Podem acontecer certos problemas na instalação do Angular CLI. Por exemplo, você
pode estar com uma versão bem desatualizada do NodeJS e do npm. Assim, garanta
sempre que seu ambiente de desenvolvimento esteja com as versões corretas desses
programas.
Outra situação é se você tiver problemas para fazer uma instalação global. Se for o
caso, faça uma instalação local (sem o -g na linha de comando) e execute o
programa ng que está no caminho ./node_modules/.bin/ng .
Comandos
O Angular CLI disponibiliza uma série de comandos, que são fornecidos como parâmetros
para o programa ng. Além disso, cada comando possui diversas opções (ou flags). Faça o
exercício de se acostumar a essas opções para aumentar a sua produtividade.
Os principais comandos serão apresentados nas seções seguintes.
help
O comando help é muito importante porque apresenta uma documentação completa do
Angular CLI, ou seja, todos os comandos e todas as suas opções.
Exemplo: documentação completa
ng help
Essa linha de comando apresenta todos os comandos e todas as suas opções.
Exemplo: documentação do comando new :
ng help new
Essa linha de comando apresenta apenas as opções do comando new .
As opções de cada comando são sempre precedidas de -- (dois traços). Algumas opções
aceitam um valor e geralmente possuem um valor padrão.
Exemplo: comando new com duas opções
ng new escola-app --skip-install --routing true --minimal true
Angular CLI
10
Essa linha de comando fornece três opções para o comando new : --skip-install , --
routing (que recebe o valor true ) e --minimal (que recebe o valor true ).
new
O comando new é utilizado para criar um projeto (um software Angular). Exemplo:
ng new escola-app
Quando a linha de comando do exemplo for executada será criado um software Angular
chamado escola-app e estará em um diretório chamado escola-app , localizado a partir de
onde a linha de comando estiver sendo executada.
A parte mais interessante de criar um projeto Angular com esse comando é que ele cria
todos os arquivos necessários para um software bastante simples, mas funcional.
Para cada comando do Angular CLI é possível informar opções. Asopções mais usadas do
comando new são:
--skip-install : faz com que as dependências não sejam instaladas
--routing : se for seguida de true , o comando cria um módulo de rotas (isso será
visto em outro capítulo
generate
O comando generate é utilizado para criar elementos em um projeto existente. Para
simplificar, é bom que o Angular CLI seja executado a partir do diretório do projeto a partir
de agora. Por meio desse comando é possível criar:
class
component
directive
enum
guard
interface
module
pipe
service
Como acontece com outros comandos, o generate também pode ser utilizado por meio do
atalho g .
Exemplo: criar component ListaDeDisciplinas
Angular CLI
11
ng generate component ListaDeDisciplinas
Essa linha de comando criará o diretório ./src/app/lista-de-disciplinas e outros quatro
arquivos nesse mesmo local:
lista-de-disciplinas.component.css
lista-de-disciplinas.component.html
lista-de-disciplinas.component.spec.ts
lista-de-disciplinas.component.ts
É importante notar que esses arquivos não estão vazios, mas já contêm código que
mantém o software funcional e utilizável. Por isso o Angular CLI considera que as opções
(class, component etc.) são como blueprints (ou plantas, em português). Outra coisa
importante é que o Angular CLI também modificará o arquivo ./src/app/app.module.ts se
necessário (ou outro arquivo que represente um módulo que não seja o root module --
mais sobre isso depois).
serve
O comando serve compila o projeto e inicia um servidor web local. Por padrão o servidor
web executa no modo --watch , que reinicia o comando novamente, de forma incremental,
sempre que houver uma alteração em algum arquivo do projeto, e --live-reload , que
recarrega o projeto no Browser (se houver uma janela aberta) sempre que houver uma
mudança.
build
O comando build compila o projeto, mas não inicia um servidor web local. Ao invés disso,
gera os arquivos resultantes da compilação em um diretório indicado.
Veremos mais sobre esses e outros comando no decorrer do livro.
Angular CLI
12
[info] Espera, compilação?
Como já disse, você pode utilizar JavaScript ou TypeScript ao desenvolver projetos
Angular. Isso não é bem assim. Na prática, geralmente você encontrará a
recomendação (senão uma ordem) de utilizar TypeScript. O problema é que seu
Browser não entende TypeScript, por isso é necessário um processo de tradução do
código TypeScript para JavaScript. E não é só isso. Há vários recursos utilizados no
projeto Angular criado com TypeScript que precisam de ajustes para que funcionem no
seu Browser.
Por isso os comandos serve e build são tão importantes e -- por que não dizer? --
browser-friendly =)
Angular CLI
13
Software de gerenciamento escolar
[info] Objetivo do capítulo:
apresentar informações sobre gerenciamento escolar
apresentar requisitos do software de gerenciamento escolar
Neste livro você verá informações técnicas sobre desenvolvimento de software web com
Angular e isso é colocado em prática por meio do desenvolvimento de um software de
gerenciamento escolar. Antes disso, entretanto, vamos ao contexto: gerenciamento escolar.
Contexto da escola
No contexto desse livro o gerenciamento escolar é aplicado em uma escola fictícia com as
seguintes características:
a estrutura de pessoal (recursos humanos) tem as seguintes características:
funcionários são pessoas contratadas para ocupar cargos e realizar funções
um funcionário tem um cargo
um funcionário tem uma ou mais funções
um funcionário pode ser: administrativo ou docente
a estrutura acadêmica da escola tem as seguintes características:
há três níveis de ensino: ensino infantil, ensino fundamental, ensino médio
cada nível de ensino pode ser organizado em anos de ensino e cada um possui
disciplinas
a relação entre nível de ensino e ano de ensino é chamada de estrutura curricular,
cada qual com disciplinas e suas cargas horárias anuais
uma turma é quando um item do componente curricular é ministrado durante um
ano letivo e tem as seguintes características:
pode ter um ou mais professores por disciplina
pode ter um ou mais alunos
matrícula é quando um aluno é matriculado em uma turma e cursa todas
as disciplinas do ano letivo
fisicamente, está presente em uma sala de aula (sala padrão)
uma aula é quando conteúdos didáticos de uma disciplina da turma são
trabalhados com professores e alunos em horários específicos na semana
uma aula pode ter participação de um ou mais professores [da turma]
uma aula pode ser realizada na sala de aula padrão da turma ou em
instalação física
Software de gerenciamento escolar
14
uma aula é realizada em um dia da semana, em um horário específico
(informação utilizada para compor a grade horária)
em cada aula o professor registra a frequência do aluno (presença ou falta)
cada aula tem a duração de 50 minutos e conta como uma hora (na Carga
Horária)
os conteúdos das aulas são organizados em bimestres e, por isso, são realizadas
avaliações bimestrais, totalizando quatro (para cada disciplina) no ano letivo
ao final do ano letivo:
as quatro notas são utilizadas para compor a nota final, que é uma média
aritmética das notas dos bimestres
o aluno é considerado "APROVADO" na disciplina se tiver média aritmética
igual ou superior a 6,0 e frequência superior a 75%
o aluno que não conseguir nota final suficiente pode realizar uma prova de
recuperação, cuja nota substitui a nota final. Por fim, se o aluno não obtiver
nota igual ou superior a 6,0 na recuperação ele é considerado "REPROVADO"
em situações excepcionais o aluno pode ser considerado "APROVADO" em
conselho de classe (uma reunião realizada ao final do ano letivo com os
professores)
com exceção do ensino infantil, o aluno só pode ser matriculado no ano seguinte
se tiver sido aprovado em todas as disciplinas do ano corrente
a estrutura física da escola tem as seguintes características:
uma instalação física pode ser: prédio, sala admnistrativa, laboratório ou sala de
aula
Para ilustrar esse contexto, alguns exemplos de estruturas curriculares:
Figura 2.3.1 - Exemplo de estrutura curricular do Ensino Médio - 1º ao 5º ano
Software de gerenciamento escolar
15
Figura 2.3.2 - Exemplo de estrutura do Ensino Fundamental - 6º ao 9º ano
Figura 2.3.3 - Exemplo de Estrutura curricular do Ensino Médio
Software de gerenciamento escolar
16
Figura 2.3.4 - Exemplo de Estrutura curricular do Ensino Infantil
Gerenciamento escolar
Com base nas características da escola usada nesse contexto, o software de
gerenciamento escolar compreende os seguintes requisitos conforme os atores:
gerente do sistema
gerenciar o cadastro de usuários e permissões de acesso
gerente administrativo
gerenciar o cadastro de cargos
gerenciar o cadastro de funções
gerenciar o cadastro de funcionários
gerenciar o cadastro de instalações físicas
gerente de ensino
gerenciar o cadastro de disciplinas
gerenciar o cadastro de níveis de ensino
gerenciar o cadastro de estruturas curriculares
gerenciar o cadastro de turmas (bem como professores nas turmas e horários)
gerenciar o cadastro de alunos (geral)
gerenciar o cadastro de matrículas (alunos nas turmas)
registrar aprovação ou reprovação excepcional em conselho de classe
professor
ver as turmas em que ministra disciplinas
ver os alunos das turmas em que ministra disciplinas
ver a agenda de aula das turmas em que ministra disciplinas (turmas, disciplinas,
salas de aula, dias da semana e horários de aula)
ver a agenda de aula da escola (todas as turmas do ano letivo)
gerenciar o cadastro de frequência dos alunos (nas suas turmas e disciplinas)
Software de gerenciamento escolar
17
gerenciar as notas das avaliações bimestrais dos alunos (nas suas turmas e
disciplinas)
aluno (e/ou pais ou responsáveis)
ver a estrutura curricular do nível e ano de ensino em que está matriculado
ver a estrutura curricular de todos os níveis e anos de ensino
ver a agenda de aula da turma em que está matriculado (disciplinas, professores,
salas de aula, dias da semana e horários de aula)ver o histórico acadêmico (notas e situação final para as turmas cursadas)
Uma descrição mais completa dos requisitos pode ser encontrada aqui:
http://200.199.229.99/reqman/projects/3/documentation.pdf.
Software de gerenciamento escolar
18
http://200.199.229.99/reqman/projects/3/documentation.pdf
Criando o projeto
[info] Objetivos do capítulo:
demonstrar como utilizar o Angular CLI para criar o projeto angular-escola
apresentar os requisitos de software que serão implementados no capítulo
Branch: iniciando
O capítulo Angular CLI apresentou essa ferramenta, comandos e opções. A primeira
utilização da ferramenta está na criação do projeto com o comando:
ng new angular-escola
Isso fará com que seja criado um diretório angular-escola no local onde o comando foi
executado. Como já informado, o diretório contém código-fonte de um software Angular
funcional. Para vê-lo em execução acesse o diretório angular-escola e execute o
comando:
npm start
Esse script executa o comando ng serve do Angular CLI e cria um servidor web local, por
padrão na porta 4200. Para ver o software em funcionamento, abra o endereço
http://localhost:4200 no seu browser de preferência. Mais para frente podemos discutir
um pouco mais a saída desse comando, porque é muito interessante. O resultado deverá
ser semelhante ao da figura a seguir.
Criando o projeto
19
https://github.com/jacksongomesbr/angular-escola/tree/iniciando
Figura 2.4.1 - Versão inicial do software em execução no Browser
Esse não é um comando do Angular CLI. Antes, é um comando (script) que está definido no
arquivo package.json .
O arquivo package.json
O arquivo package.json contém informações do projeto, scripts e dependências. O arquivo
criado pelo Angular CLI tem mais ou menos o seguinte conteúdo:
Criando o projeto
20
{
"name": "angular-escola",
...
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
...
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"core-js": "^2.4.1",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular/cli": "1.6.6",
"@angular/compiler-cli": "^5.2.0",
"@angular/language-service": "^5.2.0",
...
}
}
Por ser um conteúdo em formato JSON, o interpretamos da seguinte forma:
name : é o atributo que define o nome do projeto para o NodeJS (nesse ponto, não tem
a ver com o Angular)
scripts : é o atributo que contém scripts que podem ser executados no prompt
(exemplos: npm start e npm run build ). Nesse caso esses scripts usam o Angular
CLI (veja os valores dos atributos start e build )
dependencies : é o atributo que define as dependências do projeto. Basicamente, as
dependências atuais são padrão para software desenvolvido em Angular. Importante
notar que o nome de cada atributo determina o pacote (como @angular/core ) e seu
valor determina a versão (como ̂ 5.2.0 , indicando a versão desses pacotes do
Angular)
devDependencies : é o atributo que define as dependências de desenvolvimeto. São
ferramentas que não geram código-fonte que será distribuído junto com o software que
está sendo desenvolvido
Criando o projeto
21
Examinando o código-fonte#0
Agora que você já conhece um pouco mais da estrutura do software Angular, do Angular
CLI e do npm, vamos dar uma olhada no código-fonte que está gerando o software atual,
começando pelo AppComponent e seu Template, que está no arquivo
./src/app/app.component.html , cujo trecho de conteúdo é apresentado a seguir.
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN...Pg==">
</div>
...
Se HTML for familiar pra você então não há problema para entender o código do Template.
Na verdade, tem algo diferente ali perto de Welcome to e vamos falar disso daqui a pouco.
Agora, o Controller do mesmo componente, no arquivo ./src/app/app.component.ts , com
código-fonte a seguir:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
}
Há pouco conteúdo, mas já são usados vários conceitos do Angular, vamos examinar um-a-
um:
1. a primeira linha usa a instrução import para incluir algo chamado Component e que é
disponibilizado pelo pacote @angular/core .
2. Component é uma annotation function e é utilizada pelo Angular para adicionar
metadados à classe AppComponent .
3. Os metadados são fornecidos como parâmetro para a função Component :
i. selector : indica que o seletor é app-root . E o que é seletor? Veremos daqui a
pouco
ii. templateUrl : indica que o arquivo utilizado para o template do componente é
./app.component.html (que vimos há pouco)
Criando o projeto
22
iii. styleUrls : é um array que indica quais são os arquivos CSS utilizados no
componente. Nesse caso, está sendo utilizado apenas o arquivo
./app.component.css
4. a instrução export é utilizada para que outros arquivos possam utilizar a classe
AppComponent
Antes de prosseguirmos, perceba que há um atributo da classe AppComponent chamado
title e tem o valor 'app' (onde já vimos isso?).
A figura a seguir ilustra a estrutura do AppComponent .
Figura 2.4.2 - Estrutura do component AppComponent
Como mostra a figura o componente AppComponent é composto por Template, Controller e
Model. O Template está definido no arquivo ./src/app/app.component.html , enquanto o
Controller e o Model estão definidos no arquivo ./src/app/app.component.ts . Template e
Controller são mais fáceis de identificar nessa arquitetura. Enquanto o primeiro está no
arquivo HTML o segundo é a classe no arquivo TypeScript. Entretanto, onde está o Model?
Antes de responder essa pergunta faça um alteração no Controller: altere o valor do atributo
title para, por exemplo, 'Angular' .
Se você não tiver interrompido a execução do script npm start acontecerá uma
recompilação incremental automaticamente e a janela do Browser será recarregada. Na
página, o que está logo depois de Welcome to ? Isso mesmo, a palavra "Angular". Que
mágica é essa? Nenhuma mágica! Veja novamente o Template, mais de perto:
<h1>
Welcome to {{ title }}!
</h1>
Criando o projeto
23
Nesse momento você já deve ter aprendido que o trecho {{ title }} é responsável por
fazer com que o valor do atributo title to Controller apareça no Browser. Isso é resultado
do processo de Data binding:
analisa o Controller e identifica métodos e atributos
interpreta e analisa o Template e procura por, por exemplo, conteúdo que esteja entre
{{ e }}
utiliza o recurso de interpolação, que faz com a expressão dentro de {{ e }} seja
interpretada e gere uma alteração no conteúdo do Template ao ser apresentado no
Browser. Nesse caso {{ title }} significa: mostre o valor de title .
A figura a seguir ilustra os elementos desse processo.
Figura 2.4.3 - Relação entre Model-Template-Controller no AppComponent
Aqui aprendemos algo um conceito importante: o Angular interpreta o Template. Isso
significa que todo o seu conteúdo, HTML e CSS, é interpretado pelo Angular antes de ser
entregue para o Browser. É isso que faz com que o recurso de interpolação funcione. É por
isso, também, que Data binding é tão importante no Angular. Ele é responsável por fazer
com que o atributo title , que compõe o Model (ah, agora sim!), esteja disponível para ser
usado no Template. Além disso, qualquer alteração no valor desse atributo representará
uma alteração na visualização do componente no Browser.
Se você se lembrar da discussão sobreAngular ser MVC ou MVVM é aqui que o
entendimento pesa a favor do segundo. O Controller não está desassociado do Model e,
por causa do Data binding, sua função inclui informar o Template de que ocorreu uma
alteração no Model, de forma que ele seja atualizado.
Criando o projeto
24
É isso, o Model é um conceito abstrato e está, geralmente, no Controller, representado por
atributos e métodos -- sim, também é possível mostrar o valor retornado por um método no
template (mais sobre isso depois).
Para finalizar essa seção falta entender como o AppComponent é apresentado e qual a
relação dele com o AppModule ou outros componentes do projeto. Para isso, veja o arquivo
./src/index.html :
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular Escola</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
Ele é o primeiro arquivo que o Browser acessa, embora não somente com esse conteúdo.
Quando o Angular compila o código-fonte ele entrega um pouco mais, veja:
<!doctype html>
<html lang="en">
...
</head>
<body>
<app-root></app-root>
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="styles.bundle.js"></script>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
</body>
</html>
Esse é um trecho do código-fonte visualizado pelo Browser. Perceba as linhas com
elementos script antes da tag </body> . Esses elementos não estão no código-fonte
original, eles foram embutidos aí pelo processo de compilação do Angular (que foi iniciado
quando o script npm start foi executado). Não precisa fazer isso agora (sério!) mas em
algum momento você vai perceber que o conteúdo daqueles arquivos indicados nos
elementos script é realmente muito importante porque eles representam o resultado da
Criando o projeto
25
tradução de todo o código-fonte do projeto em conteúdo que o Browser consegue
interpretar. Destaque para o arquivo main.bundle.js , que contém o conteúdo do arquivo
./src/main.ts cujo trecho é:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
...
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
O método platformBrowserDynamic() retorna um objeto que contém o método
boostrapModule() que, ao ser executado, recebe como parâmetro a classe AppModule , que
será utilizada para representar o root module. Lembra disso? No capítulo Iniciando com o
Angular vimos que o root module é o módulo mais importante de todo projeto Angular,
porque é o primeiro a ser executado (agora você deve estar entendendo isso).
Certo, então o Angular carrega primeiro o index.html , depois usa o AppModule como root
module, mas isso ainda não explica como o AppComponent passou a aparecer no Browser.
Para entender isso, veja o arquivo ./src/app/app.module.ts , que define o AppModule :
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
As três primeiras linhas importam os módulos BrowserModule e NgModule dos seus
respectivos pacotes, bem como a classe AppComponent . Como você já sabe, o Angular
utiliza annotation functions para adicionar metadados a classes. Aqui está sendo utilizada
a função NgModule() que transforma a classe AppModule em um módulo para o Angular. O
objeto passado como parâmetro para a função possui características importantes a
destacar:
declarations : é um vetor que contém componentes do módulo (isso mesmo,
Criando o projeto
26
AppComponent está contido no AppModule );
imports : é um vetor que contém as dependências (outros módulos);
bootstrap : é um vetor que contém os componentes que devem ser utilizados quando
uma instância do AppModule passar a existir (isso, indicado ali no arquivo
./src/main.ts ).
As coisas estão ficando mais conectadas agora e a figura a seguir ilustra isso.
É isso! O elemento app-root é interpretado corretamente pelo Browser quando recebe o
arquivo index.html porque ele está indicado como selector do AppComponent , que é o
componente executado pelo AppModule , qué o root module. O selector do AppComponent
é utilizado pelo Angular para saber onde apresentar o component no Template. O valor
'app-root' indica que o Angular deve procurar um elemento com esse nome. E é o que
acontece.
Viu? Não é mágica. É software!
O que vem a seguir?
Como agora você já deve estar entendendo melhor o funcionamento do Angular e o código-
fonte do projeto, é hora de sujar as mãos =) No capítulo a seguir você vai implementar o
requisito: ver as disciplinas.
Criando o projeto
27
Depurando código no browser
[info] Objetivos do capítulo:
demonstrar como utilizar o recurso de depuração de código do Browser
Branch: iniciando
Devido ao processo de compilação/tradução do código-fonte para que o browser execute o
software os recursos de inspeção do código-fonte/elementos apresenta o que o browser
interpreta, ou seja, o resultado do processo de compilação/tradução. A figura a seguir ilustra
a tela de um browser com o painel das ferramentas de desenvolvedor.
Enquanto isso é útil para o browser, não permite que o desenvolver possa acompanhar a
execução de partes do software observando o código-fonte original.
Entretanto, a mesma ferramenta dá acesso à importante funcionalidade de depuração de
código. Para isso, basta acessar o painel Sources e, na aba Network, à esquerda, acessar
o nó webpack:// e seu filho que corresponde ao caminho do projeto do software no
ambiente local (ex: D:/developer/angular-escola ), como mostra a figura a seguir.
Depurando código no browser
28
https://github.com/jacksongomesbr/angular-escola/tree/iniciando
A figura demonstra que o arquivo /src/app/app.component.ts está selecionado. Do lado
direito da lista dos arquivos a tela apresenta o código-fonte original do arquivo. No painel
que mostra o código-fonte há um breakpoint (ponto de parada) na linha 9. Isso significa
que a execução do software no browser terá uma pausa quando da sua execução. Ainda
mais à direita da tela há uma barra de ferramentas que permitem controlar a depuração:
Os botões representam, nesta ordem:
pausar/continuar a execução do código
executar a próxima linha e não entrar no código da função/método
executar a próxima linha e entrar no código da função/método
executar a próxima linha e sair do código da função/método
desativar os breakpoints
pausar em exceções
A figura a seguir ilustra a tela durante o processo de depuração.
Depurando código no browser
29
Por fim, no painel mais à direita é possível acessar funcionalidades como inspecionar
variáveis ou expressões (Watch), ver a pilha de chamadas (Call stack) e inspecionar as
variáveis do escopo (Scope).
Depurando código no browser
30
Iniciando com teste de software
[info] Objetivos do capítulo:
apresentar o conceito de teste de software
demonstrar como escrever testes com o Angular utilizando Karma e Jasmine
Branch: iniciando
Teste de software é uma área da Engenharia de Software que envolve um conjunto de
práticas para garantir a qualidade de software conforme requisitos e expectativas dos
interessados, clientes ou desenvolvedores. Durante esse processo há geração de
informação e conhecimento que permite entender o comportamento do software em
situações simuladas ou com dados fictícios.
Uma forma de avaliar a qualidade do software utilizando testede software é utilizar
mecanismos que permitam identificar se o software está operando como esperado, fazendo
o que deveria fazer.
Ao criar o projeto com o Angular CLI, junto com o componente AppComponent também é
criado o arquivo src/app/app.component.spec.ts . Esse arquivo TypeScript descreve testes
utilizando Jasmine. Por definição Jasmine é um framework de teste de software que segue
a técnica BDD (Behaviour Driven Development). Embora o Jasmine possa ser utilizado
diretamente para testar JavaScript o Angular utiliza duas outras tecnologias:
Karma, uma ferramenta utilizada para executar os testes escritos em Jasmine (por
definição, Karma é um test runner); e
Protractor, um framework para testes end-to-end (e2e).
Então, um trecho do arquivo src/app/app.component.spec.ts :
Iniciando com teste de software
31
https://github.com/jacksongomesbr/angular-escola/tree/iniciando
https://jasmine.github.io/index.html
https://karma-runner.github.io/1.0/index.html
http://www.protractortest.org
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
...
}));
it('should create the app', async(() => {
...
}));
it(`should have as title 'Angular'`, async(() => {
...
}));
it('should render title in a h1 tag', async(() => {
...
}));
});
Testes escritos em Jasmine são chamados de specs e, por padrão, precisam estar em
arquivos com nomes que terminem em .spec.ts (isso está definido no arquivo
src/tsconfig.spec.json ).
Depois das primeiras linhas com import há a primeira utilização de um recurso do Jasmine:
a chamada do método describe() . Esse método cria uma suíte de testes (um grupo de
testes). Os parâmetros para o método são:
o nome da suíte de testes; e
uma função que define suítes internas e specs.
No exemplo, o nome da suíte é 'AppComponent' e a função que define as suítes internas e
as specs está definindo três testes.
As specs (testes) são definidas por meio de uma chamada ao método it(). Esse método cria
uma spec. Os parâmetros do método são:
o nome da spec (na prática, uma descrição do que a spec está testando);
uma função que descreve o código do teste; e
um número que representa o tempo limite de execução do teste (timeout).
No caso do exemplo há três specs:
should create the app (deveria criar o app -- ou o app deveria ser criado)
should have title as 'Angular' (deveria ter o título como 'Angular' -- ou o título
deveria ser 'Angular')
should render title in a h1 tag (deveria renderizar o título em uma tag h1 -- ou o
título deveria estar em uma tag h1)
Iniciando com teste de software
32
Percebeu o "should" (deveria) no nome de cada spec? Isso faz parte da filosofia BDD que
indica que cada spec checa se as expectativas sobre o teste são válidas (passam) ou não
(falham). Antes de ver mais detalhes, vamos testar o software. Execute o comando:
npm test
Esse comando executa o script "test" definido no arquivo package.json . Na prática, ele
executa o comando ng test .
Dentro de instantes você deveria ver uma janela do browser mostrando algo semelhante ao
da figura a seguir.
Figura 2.6.1 - Janela do browser demonstrando o resultado dos testes iniciais
Note que a URL do browser é http://localhost:9876 . Essa é a URL padrão que apresenta
os resultados do teste do software. A tela apresenta várias informações importantes:
versão do sistema operacional (Windows 10)
Iniciando com teste de software
33
versão do browser (Chrome 63)
versão do Karma (1.7.1)
versão do Jasmine (2.6.4)
tempo total de execução dos testes (0.187s)
quantidade de specs (3)
quantidade de falhas é zero (0), o que indica que todos os testes passaram (ou que
não houve falha nos testes)
Além disso a tela apresenta o próprio componente ( AppComponent ) em execução. Ainda,
também há informações no prompt. Você deveria ver algo como o seguinte:
15 12 2017 01:56:31.937:WARN [karma]: No captured browser, open http://localhost:9876/
15 12 2017 01:56:32.381:INFO [Chrome 63.0.3239 (Windows 10 0.0.0)]: Connected on socke
t ...
Chrome 63.0.3239 (Windows 10 0.0.0): Executed 0 of 3 SUCCESS (0 secs / 0 secs)
Chrome 63.0.3239 (Windows 10 0.0.0): Executed 1 of 3 SUCCESS (0 secs / 0.101 secs)
Chrome 63.0.3239 (Windows 10 0.0.0): Executed 2 of 3 SUCCESS (0 secs / 0.143 secs)
Chrome 63.0.3239 (Windows 10 0.0.0): Executed 3 of 3 SUCCESS (0 secs / 0.184 secs)
Chrome 63.0.3239 (Windows 10 0.0.0): Executed 3 of 3 SUCCESS (0.092 secs / 0.184 secs)
Essas informações indicam dados adicionais:
o primeiro teste executou em 0.101s
o segundo teste executou em 0.143 - 0.101 = 0.042s
o terceiro teste executou em 0.184 - 0.143 = 0.041s
Ok, agora vamos a detalhes dos testes mas, antes de ver cada teste individualmente, o
segundo parâmetro do método describe() chama o método beforeEach() , que é utilizada
para realizar uma espécie de setup dos testes, executando um código antes deles:
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
...
A classe TestBed é um utilitário do Angular para testes de software. Seu método
configureTestingModule() cria um módulo específico para executar os testes que serão
descritos a seguir. Pense nisso como um módulo mais enxuto que os utilizados no restante
Iniciando com teste de software
34
do software, voltando apenas para esses testes. Por isso você deveria entender que o
parâmetro fornecido para o método é muito similar aos metadados de um módulo e, nesse
caso, indicam os componentes do módulo (atributo declarations ). Portanto, o setup
permite que cada teste utilize o componente AppComponent .
Primeiro teste
O trecho de código a seguir apresenta o primeiro teste.
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
Esse primeiro teste verifica se foi possível criar uma instância do componente
AppComponent , o que é feito por meio de uma sequência de passos:
1. criar uma instância de AppComponent : isso é feito por meio do método
TestBed.createComponent() (armazenado na variável fixture ) que retorna uma
instância de ComponentFixture , referência ao ambiente de execução dos testes. O
ambiente de execução dos testes dá acesso ao DebugElement , que representa o
elemento do DOM que representa o componente
2. acessar a instância do componente ( fixture.debugElement.componentInstance ),
armazenada na variável app ;
3. criar uma expectativa para o teste
Uma expectativa é criada por meio do método expect() . Uma expectativa representa o
comportamento esperado do software: que app (uma instância do AppComponent ) não seja
null . O parâmetro de expect() indica a expressão que será analisada, comparada, com
outra expressão ou valor. Nesse caso a comparação é realizada por meio do método
toBeTruthy() . Se a expectativa não for satisfeita o teste falhará. Isso seria o
comportamento obtido, por exemplo, por causa de um erro de tempo de execução
(runtime).
Segundo teste
O trecho de código a seguir apresenta o segundo teste.
Iniciando com teste de software
35
it(`should have as title 'Angular'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Angular');
}));
A sequência de passos é semelhante à do primeiro teste. A diferença está na expectativa: o
teste deseja verificar se o atributo title da instância do AppComponent ( app ) tem valor
igual a 'Angular' . Novamente, a expectativa é criada por meio do método expect() .
Dessa vez a comparação entre o valor esperado ( app.title ) e o valor obtido ( 'Angular' )
é feita por meio do método toEqual().
Com isso aprendemos que é possível testar membros de um componente (atributos e
métodos), o que é muito valioso.
Terceiro teste
O trecho de código a seguir apresenta o terceiro teste.
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to Angular!');
}));
Em relação aos testes anteriores a sequência de passos inclui uma chamada ao método
ComponentFixture.detectChanges() . Você deveria aprender, nos capítulos seguintes, que o
Angular utiliza, muito, um recurso chamado data binding. Esse recurso faz com que o valor
do atributo title seja apresentado no Template. Para checar se esse é o comportamento
obtido é necessário, primeiro, identificar que ocorreu data binding. Uma forma de fazer isso
é usando o método detectChanges() .
Outra diferença é que o teste lida com o DOM depois de ter sido transformado pelo Angular,
ou seja, na prática, em como o usuário está vendo o componente no momento. Por causa
disso não é utilizada uma instância do componente, mas uma referência ao DOM compilado
(o elemento DOM na raiz do componente, na verdade). Isso é feito por meio de
fixture.debugElement.nativeElement (armazenado na variável compiled ).
Por fim, a expectativa é um pouco mais complexa, pois envolve lidar com o DOM. Para isso
ocorre o seguinte:
1. encontrar o elemento h1 no DOM: isso é feito por meio do método querySelector() ;
Iniciando com teste de software
36
2. comparar o conteúdo textual do elemento h1 com a string Welcome to Angular! : isso
é feito por meio de uma expectativa sobre o valor do atributo textConent do objeto que
representa o elemento h1 conter a string desejada, comparação implementada pelo
método toContain() .
You shall not pass! Ou provocando uma falha
nos testes
Suponha que você modifique o valor do atributo title do AppComponent para 'Web' . O
que os testes diriam? Se os testes ainda estiverem em execução, apenas observe a janela
do browser que mostra o resultado dos testes (ilustrada pela figura a seguir).
Iniciando com teste de software
37
A saída dos testes mostra claramente que algo errado aconteceu: dos três testes, dois
falharam. A saída indica cada teste que falhou (detalhe em vermelho):
o segundo teste falhou porque a string 'Web' não igual a 'Angular' ; e
o terceiro teste falhou porque a string 'Welcome to Web!' não contém 'Welcome to
Angular!' .
Percebeu que os testes são executados novamente sempre que há uma mudança no
código do software? Esse é o comportamento padrão, mas se você precisar desligá-lo
precisará de uma mudança no arquivo package.json , modificando o script de teste de ( ng
test ):
...
"scripts": {
...
"test": "ng test",
...
},
...
para ( ng test --single-run ):
...
"scripts": {
...
"test": "ng test --single-run",
...
},
...
O problema disso é que, ao executar o comando npm test você verá a janela do browser
aparecer e sumir logo depois de alguns instantes. Assim, precisará lidar com a saída do
comando no prompt para identificar o que ocorreu (testes passaram ou falharam).
Iniciando com teste de software
38
[info] Resumo do capítulo:
testes de software permitem avaliar se o comportamento do software está como o
esperado
Jasmine é um framework utilizado para escrever testes usando a técnica BDD
Karma e Protractor são ferramentas do Angular para executar testes
o comando npm test é usado para executar testes
testes são definidos dentro de suítes
uma suíte é criada usando o método describe()
um teste é criado usando o método it()
um teste pode validar diversos comportamentos do software, mas cada
comportamento é validado usando o método expect() , que compara um valor
esperado com um valor obtido
testes do Angular envolvem verificar membros do componente (atributos e
método) e o DOM compilado, que é visualizado pelo cliente no browser
Iniciando com teste de software
39
Apresentando a lista de disciplinas
[info] Objetivos do capítulo:
demonstrar a implementação do requisito "apresentar as disciplinas" de forma
iterativa
demonstrar o uso das diretivas ngFor e ngIf
reforçar o entendimento do Data binding
Branches: livro-listando-disciplinas, livro-listando-disciplinas-objetos, livro-listando-
disciplinas-classes, livro-listando-disciplinas-interacao
Começando pelo Controller do AppComponent , remova o atributo title e adicione o
atributo disciplinas . Esse atributo é um array de string, contendo os nomes das
disciplinas. O conteúdo do Controller deve ficar mais ou menos assim (trecho):
export class AppComponent {
disciplinas = [
'Língua Portuguesa',
'Arte',
'Educação Física',
'Matemática',
'História',
'Geografia',
'Ciências',
'Redação',
'Língua Estrangeira Moderna - Inglês',
'Ensino Religioso'
];
}
Na sequência altere o Template do AppComponent para o seguinte conteúdo:
<h1>Disciplinas</h1>
<hr>
<ul>
<li *ngFor="let disciplina of disciplinas">
{{disciplina}}
</li>
</ul>
Parte importante do Template está no elemento li : o atributo *ngFor é, na verdade, uma
diretiva estrutural que modifica o Template no browser, gerando novos elementos com
base em uma repetição.
Apresentando a lista de disciplinas
40
https://github.com/jacksongomesbr/angular-escola/tree/livro-listando-disciplinas
https://github.com/jacksongomesbr/angular-escola/tree/livro-listando-disciplinas-objetos
https://github.com/jacksongomesbr/angular-escola/tree/livro-listando-disciplinas-classes
https://github.com/jacksongomesbr/angular-escola/tree/livro-listando-disciplinas-interacao
A diretiva, na verdade mesmo, é chamada NgForOf . Usamos uma espécie de atalho
por meio do atributo *ngFor (sem mais explicações sobre isso, por enquanto). Então,
ao ler NgForOf ou *ngFor saiba que estamos falando da mesma coisa.
A diretiva *ngFor é utilizada para repetir um elemento do Template. Nesse caso, é
justamente essa a sua aplicação: repetir o elemento li para apresentar os nomes das
disciplinas. A repetição é realizada por uma expressão: let disciplina of disciplinas
significa que o *ngFor vai repetir o elemento li para cada item do array disciplinas e
cada item recebe o nome de disciplina , o que demonstra outra utilização do Data
binding. Explicando a mesma coisa de outra forma:
a repetição sempre é baseada em uma coleção de itens (ou em algo que possa ser
interpretado como tal)
o Angular cria uma variável de template (existe apenas dentro do elemento li ) com o
nome de disciplina
essa variável de template é usada como uma referência para cada item da repetição
É por isso que a interpolação funciona dentro do elemento li para apresentar o nome de
cada disciplina.
A documentação do Angular afirma que let disciplina of disciplinas é uma
microssintaxe e isso não é a mesma coisa que uma expressão usada em uma
interpolação.
Abra o aplicativo no browser. O resultado deve ser semelhante à figura a seguir.
Apresentando a lista de disciplinas
41
Figura 2.7.1 - Apresentando a lista das disciplinas
Melhorando o modelo de dados usando
objetos
A utilização de um array de strings pode ser suficiente em vários casos. Entretanto, quando
se deseja apresentar mais informações é necessário utilizar outra abordagem. Suponha,
portanto, que precisemos apresentar dois dados de cada disciplina: nome e descrição.
Uma boa forma de fazer isso é utilizar objetos.
Modifique o Controller do AppComponent para que cada item do array disciplinas seja um
objeto com dois atributos: nome e descricao , como mostra o trecho de código a seguir.
Apresentando a lista de disciplinas
42
...
export class AppComponent {
disciplinas = [
{
nome: 'Língua Portuguesa',
descricao: 'O objetivo norteador da BNCC de Língua Portuguesa é ' +
'garantir a todos os alunos o acesso aos saberes '+
'linguísticos necessários para a participação social e o exercício ' +
'da cidadania, pois é por meio da língua que ' +
'o ser humano pensa, comunica-se, tem acesso à informação, expressa ' +
'e defende pontos de vista, partilha ou ' +
'constrói visões de mundo e produz conhecimento.'
},
...
];
}
Essa notação do TypeScript para representar objetos de forma literal é muito útil quando
não se tem uma classe para descrever objetos.
Na sequência ocorre uma pequena alteração no Template:
<h1>Disciplinas</h1>
<hr>
<ul>
<li *ngFor="let disciplina of disciplinas">
<strong>{{disciplina.nome}}</strong>: {{disciplina.descricao}}
</li>
</ul>
A principal mudança está no fato de que as interpolações usam expressões baseadas nos
atributos do objeto disciplina .
Para ver o resultado, abra o aplicativo no browser.
Melhorando o modelo de dados usando
classes
Enquanto a utilização de objetos literais é bastante útil, também é interessante expressar o
modelo de dados utilizando classes. Para isso, crie o arquivo
./src/app/disciplina.model.ts com o conteúdo a seguir:
Apresentando a lista de disciplinas
43
export class Disciplina {
nome: string;
descricao: string;
constructor(nome: string, descricao?: string) {
this.nome = nome;
this.descricao = descricao;
}
}
Para utilizar mais recursos do TypeScript, a classe Disciplina possui dois atributos: nome
e descricao , ambos do tipo string . O método constructor() recebe dois parâmetros,
ambos do tipo string : nome e descricao (que é opcional -- note o uso de ? ). Para
concluir, a classe é disponibilizada para uso externo por meio da instrução export .
Para usar a classe Disciplina , modifique o Controller do AppComponent :
import {Component} from '@angular/core';
import {Disciplina} from './disciplina.model';
...
export class AppComponent {
disciplinas = [
new Disciplina('Língua Portuguesa', 'O objetivo norteador ...'),
new Disciplina('Educação Física', 'A Educação Física é ...'),
...
];
}
Perceba que os elementos do atributo disciplinas agora são instâncias da classe
Disciplina . O mais interessante é que o Template não precisa ser alterado, já que está
utilizando objetos com a mesma estrutura de antes, ou seja, espera que os itens do atributo
disciplinas sejam objetos que tenham os atributos nome e descricao .
Adicionando interação com o usuário
A apresentação de todos os dados das disciplinas na tela pode ser melhorada usando
interação com o usuário. Suponha que o usuário veja a lista das disciplinas contendo
apenas seus nomes; ele deve clicar no nome de uma disciplina para ver sua descrição.
Além disso, para dar uma resposta visual para o usuário quando ele clicar no nome da
disciplina, considere que será usada uma formatação em negrito.
Para implementar esses requisitos vamos usar outros recursos do Angular.
Começando pelo Controller do AppComponent são acrescentados:
Apresentando a lista de disciplinas
44
o atributo selecionado
o método selecionar()
export class AppComponent {
selecionado = null;
disciplinas = [
new Disciplina('Língua Portuguesa', 'O objetivo norteador da BNCC ...'),
...
];
selecionar(disciplina) {
this.selecionado = disciplina;
}
}
O atributo selecionado será utilizado para que o Controller saiba qual disciplina o usuário
selecionou. O usuário fará isso por meio de uma chamada para o método selecionar() ,
que recebe o parâmetro disciplina e o atribui para o atributo selecionado . Quando isso
acontecer (o usuário selecionar uma disciplina) o software apresentará a sua descrição.
Para utilizar mais recursos do desenvolvimento front-end, dessa vez também alteramos o
arquivo ./src/app/app.component.css :
.selecionado {
font-weight: bold;
}
li p:nth-child(1) {
cursor: pointer;
font-size: 12pt;
}
li p:nth-child(1):hover {
font-weight: bold;
}
li p:nth-child(2) {
font-size: 10pt;
}
O arquivo CSS do AppComponent possui regras de formatação:
seletor .selecionado : para deixar o nome da disciplina selecionada em negrito
seletor li p:nth-child(1) : para que o cursor do mouse sobre o nome da disciplina
seja pointer (semelhante ao comportamento do cursor em links) e tenha fonte de
tamanho 12pt
seletor li p:nth-child(1):hover : para que o nome da disciplina sob o mouse fique em
Apresentando a lista de disciplinas
45
negrito
seletor li p:nth-child(2) : para que a fonte da descrição seja de tamanho 10pt
Agora, o Template:
<h1>Disciplinas</h1>
<hr>
<ul>
<li *ngFor="let disciplina of disciplinas" (click)="selecionar(disciplina)">
<p [class.selecionado]="selecionado == disciplina">{{disciplina.nome}}</p>
<p *ngIf="selecionado == disciplina">{{disciplina.descricao}}</p>
</li>
</ul>
A primeira coisa a destacar é o recurso que proporciona a interação com o usuário. Como já
comentado, o requisito determina que o usuário possa clicar no nome da disciplina. Isso é
um ação do usuário para a interface. O Angular implementa isso por meio de eventos e de
uma sintaxe própria: o atributo (click) no elemento li indica que estamos fornecendo
um tratador para o evento click ; o tratador é o valor do atributo, ou seja, uma expressão
que representa a chamada do método selecionar(disciplina) , definido no Controller.
Outra característica importante é a chamada do método selecionar() fornece como
parâmetro disciplina , que é o item da repetição realizada pelo *ngFor . Se você voltar no
código do Controller vai ver que esse parâmetro é atribuído para o atributo selecionado ,
que faz justamente isso: guarda uma referência para a disciplina selecionada.
O próximo passo é garantir o determinado no requisito no sentido da apresentação. Para
fazer com que a disciplina selecionada fique em negrito vamos aplicar uma classe CSS
chamada selecionado em um elemento p que contém o nome da disciplina sempre que a
disciplina da repetição do *ngFor for igual à disciplina selecionada. O Angular fornece
um recurso para modificar características de elementos do HTML por meio de expressões.
Esse recurso está sendo usado na tag <p> : [class.selecionado] parece como um atributo
do HTML, mas é tratado pelo Angular antes de o HTML ser fornecido para o browser,
modificando o atributo class e inserindo nele a classe CSS chamada selecionado . Nesse
sentido, o valor do atributo é uma expressão e, nesse caso, uma expressão booleana:
selecionado == disciplina determina, então, que o elemento p terá o atributo class
com valor selecionado se a disciplina atual da iteração do *ngFor for igual à disciplina
selecionada.
Para finalizar, o software deve apresentar a descrição da disciplina selecionada. Para isso
vamos usar outra diretiva, a NgIf . Veja o trecho:
Apresentando a lista de disciplinas
46
<li *ngFor="let disciplina of disciplinas" ...>
...
<p *ngIf="selecionado == disciplina">{{disciplina.descricao}}</p>
</li>
A diretiva *ngIf é aplicada no elemento p usado para apresentar a descrição da
disciplina. Como o propósito dessa diretiva é funcionar como um condicional o valor que o
atributo recebe é uma expressão booleanda: selecionado == disciplina . Se a expressão
for verdadeira (se a disciplina da repetição do *ngFor for igual à disciplina selecionada),
o elemento p estará presente no HTML, caso contrário, não estará presente. Perceba que
usei o termo "estar presente" ao invés de "visível" porque é isso que acontece. Se a
expressão booleanda for falsa, o elemento no qual a diretiva *ngIf é aplicada nem está
presente no HTML.
Para ilustrar isso, veja um trecho da estrutura HTML no browser:
<ul _ngcontent-c0="">
<!--bindings={"ng-reflect-ng-for-of": "[object Object],[object Object"}-->
<li _ngcontent-c0="">
<p _ngcontent-c0="" class="selecionado">Língua Portuguesa</p>
<!--bindings={"ng-reflect-ng-if": "true"}-->
<p _ngcontent-c0="">O objetivonorteador da BNCC de Língua Portuguesa ...</p>
</li>
<li _ngcontent-c0="">
<p _ngcontent-c0="" class="">Educação Física</p>
<!--bindings={"ng-reflect-ng-if": "false"}-->
</li>
...
</ul>
Os vários comentários presentes no HTML parecem ser algo que o Angular utiliza para
tratar o comportamento da interface? É justamente isso que acontece (não precisa tentar
interpretar os comentários agora). No caso desse exemplo, o usuário clicou na disciplina
"Língua Portuguesa". Características importantes:
para o elemento li correspondente à disciplina selecionada:
o elemento p que mostra o nome da disciplina possui class="selecionado"
o elemento p que mostra a descrição da disciplina está presente no HTML
para os demais elementos li :
o elemento p que mostra o nome da disciplina não possui o atributo class
o elemento p que mostra a descrição da disciplina não existe no HTML
Apresentando a lista de disciplinas
47
Se você já tiver programado para front-end anteriormente vai entender melhor a
complexidade do que está acontecendo aqui, que o Angular não passa para o programador.
O Angular está realizando a chamada manipulação do DOM para que o HTML entregue
para o browser tenha o comportamento esperado (ou explícito no software).
Por fim, a figura a seguir ilustra o funcionando do software no browser.
Figura 2.7.2 - Execução do software no browser, listando disciplinas e interagindo com o
usuário
Pronto, esse capítulo mostrou a utilização de vários recursos do Angular para implementar
o requisito "Apresentar a lista das disciplinas".
Apresentando a lista de disciplinas
48
[info] Para resumir:
utilizar recursos da Programação Orientada a Objetos é muito bom para
representação do modelo de dados
a diretiva NgForOf é utilizada para repetir elementos no Template com base em
uma coleção de itens (um array)
a diretiva NgIf é utilizada para incluir ou excluir elementos do Template com base
em um condicional
(click) representa a sintaxe do Angular para criar tratadores de eventos
[class.selecionado] representa a sintaxe do Angular para adicionar ou remover
atributos no HTML
Apresentando a lista de disciplinas
49
Cadastrando uma disciplina
[info] Objetivos do capítulo:
demonstrar o uso de formulários e two-way data binding
implementar o requisito "Cadastrar disciplina"
Branches: livro-cadastrando-disciplina
Neste capítulo vamos começar pelo final: primeiro, veja a figura que ilustra como o software
estará ao final do capítulo.
Figura 2.8.1 - Tela do software apresentando a lista das disciplinas e o formulário de
cadastro
A tela do aplicativo tem duas funções: na primeira parte, apresenta a lista das disciplinas; na
segunda parte, apresenta um formulário que permite o cadastro de uma disciplina. A
primeira parte você já conhece do capítulo anterior. Agora, veremos os detalhes para o
formulário de cadastro.
Cadastrando uma disciplina
50
https://github.com/jacksongomesbr/angular-escola/tree/livro-cadastrando-disciplina
Antes de prosseguir, um fator importante: o projeto original criado pelo Angular CLI não
suporta formulários. Isso mesmo que você leu. O Angular trata formulários de três formas
diferentes, uma delas, a que vamos utilizar, chamada de template driven forms (algo como
formuários orientados a templates).
Para habilitar o suporte a formulários é necessário importar o módulo FormsModule no root
module:
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {AppComponent} from './app.component';
@NgModule({
...
imports: [
BrowserModule,
FormsModule
],
...
})
export class AppModule {
}
O módulo FormsModule está definido no pacote @angular/forms e é importado no
AppModule por meio do atributo imports dos metadados da classe. A partir de então a
utilização de formulários do tipo template-driven forms está garantida.
O formulário de cadastro tem dois campos: nome e descrição. O comportamento esperado
pelo usuário é preencher os valores dos campos, clicar no botão "Salvar" e ver a lista de
disciplinas atualizada, contendo a disciplina que acabou de ser cadastrada.
Para fazer isso, primeiro vamos ao Controller do AppComponent :
Cadastrando uma disciplina
51
...
export class AppComponent {
selecionado = null;
nome = null;
descricao = null;
disciplinas = [
new Disciplina('Língua Portuguesa', 'O objetivo norteador da BNCC de ...'),
...
];
salvar() {
const d = new Disciplina(this.nome, this.descricao);
this.disciplinas.push(d);
this.nome = null;
this.descricao = null;
}
}
São adicionados dois atributos e um método:
nome : está ligado ao campo nome do formulário por meio de data binding
descricao : está ligado ao campo descricao do formulário por meio de data binding
salvar() : é executado quando o usuário clicar no botão "Salvar"
Os dois atributos são inciados com null . O método salvar() cria uma instância da
classe Disciplina usando os atributos nome e descrição , respectivamente.
Posteriormente, adiciona esse objeto ao vetor disciplinas por meio do método push() .
Por fim, atribui o valor null novamente aos atributos nome e descricao .
Mas... o que significa um atributo do Controller estar ligado a um campo do formulário via
data binding? Você já sabe que data binding é o recurso do Angular que faz com que o
valor de um atributo do Controller seja apresentado no Template por meio de interpolação.
E quanto ao formulário? Vamos ao Template:
Cadastrando uma disciplina
52
<h1>Disciplinas</h1>
...
<h2>Cadastrar</h2>
<p>Use este formulário para cadastrar uma disciplina.</p>
<form #cadastro="ngForm" (submit)="salvar()">
<div>
<label for="nome">Nome</label><br>
<input type="text" id="nome" name="nome" [(ngModel)]="nome" required>
</div>
<div>
<label for="descricao">Descrição</label><br>
<textarea id="descricao" name="descricao" [(ngModel)]="descricao"
cols="70" rows="5">
</textarea>
</div>
<button type="submit" class="btn btn-primary" [disabled]="!cadastro.valid">
Salvar
</button>
</form>
Como a parte da lista das disciplinas não muda muito, vamos omitir essa parte e apresentar
o formulário, começando pela tag <form> . Em termos de HTML, a tag possui um atributo
#cadastro , com valor ngForm . Esse recurso, na verdade, é uma sintaxe do Angular para
criar uma variável (chamada cadastro ) vinculada ao formulário. Importante também
destacar o evento submit . Você já entende a sintaxe (submit) , então o tratador do evento
é uma chamada ao método salvar() .
Na sequência são definidos os campos do formulário. A parte mais importante está nas
tags input e textarea , principalmente o que, em termos de HTML, parece um atributo:
[(ngModel)] . Você já sabe a sintaxe para eventos (entre parênteses) e já viu que o Angular
consegue alterar um atributo do HTML por meio de colchetes. Essa sintaxe, que mistura as
duas anteriores, é a sintaxe do recurso two-way data binding. No campo para o nome,
[(ngModel)] tem o valor nome , para o campo descrição, tem o valor descricao . Ambos
são atributos do Controller. Assim, o two-way data binding significa que o valor do atributo
pode ser modificado por meio de uma alteração do campo de formulário correspondente, e
vice-versa:
se o valor do atributo nome for modificado no Controller, o Template será atualizado,
mostrando esse valor no input
se o valor do atributo nome for modificado no Template, por uma alteração no input ,
esse valor estará disponível no Controller
Por isso o two-way: duas vias. A figura a seguir ilustra esse processo.
Cadastrando uma disciplina
53
Figura 2.8.2 - Ilustração do two-way data binding, ligando formulário e Controller
O formulário tem mais uma característica: a entrada do usuário está passando por
validação. O elemento input para o camponome tem o atributo required , que o torna de
preenchimento obrigatório. A partir de então há um comportamento muito interessante para
o botão "Salvar": o elemento button possui um atributo disabled , que é modificado
conforme a expressão !cadastro.valid . Lembra da variável de template #cadastro ? Aqui
ela é usada como uma referência ao formulário. Portanto, se o formulário de cadastro
estiver válido, o botão "Salvar" estará habilitado para clique. Caso contrário, estará
desabilitado.
Execute o software no browser para notar o comportamento citado no capítulo.
[info] Resumo do capítulo:
Implementamos o requisito "Cadastrar uma disciplina"
Utilizamos o recurso two-way data binding
Utilizamos formulário para receber entrada do usuário
Cadastrando uma disciplina
54
Excluindo uma disciplina
[info] Objetivos do capítulo:
demonstrar a implementação do requisito "Excluir uma disciplina"
Branch: livro-excluindo-disciplina
Este é um capítulo mais simples. Basicamente os recursos utilizados já foram
apresentados, então ele é mais curto.
Primeiro, um trecho do Controller do AppComponent :
...
export class AppComponent {
selecionado = null;
nome = null;
descricao = null;
disciplinas = [
new Disciplina('Língua Portuguesa', '...'),
...
];
salvar() {
...
}
excluir(disciplina) {
if (confirm('Tem certeza que deseja excluir a disciplina "'
+ disciplina.nome + '"?')) {
const i = this.disciplinas.indexOf(disciplina);
this.disciplinas.splice(i, 1);
}
}
}
Com exceção do código omitido, que você já conheceu nos capítulos anteriores, o método
excluir() recebe como parâmetro um objeto, chamado disciplina . Esse método exclui
uma disciplina da lista, mas, antes, solicita uma confirmação do usuário (por isso está
usando a função confirm() ). Se o usuário confirmar que deseja excluir a disciplina, então
o código prossegue:
encontra o índice da disciplina na lista usando o método indexOf()
remove um elemento da lista usando o método splice()
Agora, então, um trecho do código do Template:
Excluindo uma disciplina
55
https://github.com/jacksongomesbr/angular-escola/tree/livro-excluindo-disciplina
<h1>Disciplinas</h1>
<hr>
<ul>
<li *ngFor="let disciplina of disciplinas">
{{disciplina.nome}}
<button (click)="excluir(disciplina)">
Excluir
</button>
</li>
</ul>
<h2>Cadastrar</h2>
<p>Use este formulário para cadastrar uma disciplina.</p>
<form #cadastro="ngForm" (submit)="salvar()">
...
</form>
A parte importante do template é o elemento button ao lado do nome da disciplina. Há um
tratador do evento (click) que chama o método excluir() passando como parâmetro a
disciplina atual da repetição do *ngFor .
O resultado da execução do software no browser é ilustrado pela figura a seguir.
Excluindo uma disciplina
56
Figura 2.9.1 - Execução do software no browser ilustrando a confirmação do usuário para
excluir uma disciplina
Esse capitulo é bem curto e não há muito o que resumir, mas é bom notar que os conceitos
vistos nos capítulos anteriores continuam funcionando aqui, mesmo que em aplicações
diferentes.
Excluindo uma disciplina
57
Editando dados de uma disciplina
[info] Objetivos do capítulo:
demonstrar a implementação do requisito "Editar dados de uma disciplina"
reforçar os conceitos anteriores
demonstrar a implementação mais completa de um CRUD
Branch: livro-crud-disciplina
Como o capítulo anterior, esse capítulo não é muito extenso em termos de conceitos novos.
Entretanto, ele reúne os capítulos anteriores para implementar um CRUD, que significa:
Create (cadastrar)
Retrieve (recuperar, listar, consultar)
Update (atualizar, editar)
Delete (excluir)
A figura a seguir ilustra a interface do software como construída ao final do capítulo.
Editando dados de uma disciplina
58
https://github.com/jacksongomesbr/angular-escola/tree/livro-crud-disciplina
Figura 2.10.1 - Software com o CRUD de Disciplinas
A interface continua com duas partes: uma apresentando uma lista e outra apresentando
um formulário. Na lista, para cada item, há dois botões: "Excluir" e "Editar". Ao clicar no
botão "Excluir" o software solicita uma confirmação do usuário. Ao clicar no botão "Editar"
os dados da disciplina em questão são apresentados no formulário (nome e descrição).
Nesse momento o formulário entra no modo de edição e:
se o usuário clicar em "Salvar" os dados da disciplina serão atualizados
se o usuário clicar em "Cancelar" qualquer alteração não será armazenada
O modo padrão do formulário é o cadastro. Nesse sentido se o usuário preencher os
campos (nome e descrição) e:
clicar em "Salvar", os dados serão armazenados em uma nova disciplina,
apresentando-a na lista
clicar em "Cancelar", os dados não serão armazenados
Vamos ao código-fonte. Primeiro, um trecho do Controller:
Editando dados de uma disciplina
59
...
export class AppComponent {
editando = null;
nome = null;
descricao = null;
disciplinas = [
new Disciplina('Língua Portuguesa', 'O objetivo norteador da BNCC...'),
...
];
salvar() {
if (this.editando) {
this.editando.nome = this.nome;
this.editando.descricao = this.descricao;
} else {
const d = new Disciplina(this.nome, this.descricao);
this.disciplinas.push(d);
}
this.nome = null;
this.descricao = null;
this.editando = null;
}
excluir(disciplina) {
if (this.editando == disciplina) {
alert('Você não pode excluir uma disciplina que está editando');
} else {
if (confirm('Tem certeza que deseja excluir a disciplina "'
+ disciplina.nome + '"?')) {
const i = this.disciplinas.indexOf(disciplina);
this.disciplinas.splice(i, 1);
}
}
}
editar(disciplina) {
this.nome = disciplina.nome;
this.descricao = disciplina.descricao;
this.editando = disciplina;
}
cancelar() {
this.nome = null;
this.descricao = null;
this.editando = null;
}
}
As novidades são:
adição do atributo editando : é inicializado com null e manterá uma referência para a
Editando dados de uma disciplina
60
disciplina que está sendo editada (quando houver alguma)
atualização no método salvar() : para tratar a situação do formulário (cadastrando ou
editando disciplina)
atualização no método excluir() : para tratar a situação em que o usuário quer excluir
uma disciplina que está sendo editada
adição do método editar()
O método salvar() tem um novo comportamento: se o usuário estiver editando uma
disciplina (verifica o valor do atributo editando ), então usa os dados do formulário e
atualiza os dados da disciplina que está sendo editada. Caso contrário, continua com o
comportamento anterior: usa os dados do formulário para criar uma instância de
Disciplina e adiciona no vetor disciplinas .
O método excluir() tem um novo comportamento: se o usuário estiver editando uma
disciplina (verifica o valor do atributo editando), então informa o usuário que não é possível
excluir a disciplina que está sendo editada. Caso contrário, continua com o comportamento
normal: solicita uma confirmação do usuário e exclui a disciplina.
O método editar() recebe como parâmetro um objeto que representa a disciplina a ser
editada, atualiza os campos do formulário com os atributos de disciplina e faz com que o
atributo editando receba disciplina para indicar que o formulário está no modo de
edição (há uma disciplina sendo editada).
O método cancelar() tem a função de reiniciar o formulário para os valores-padrão,
atributo o valor null para os atributos nome , descricao e editando .
Para concluir, o Template:
Editando dados de uma disciplina
61
<h1>Disciplinas</h1>
<hr>
<ul>
<li *ngFor="let disciplina of disciplinas">
{{disciplina.nome}}
<button (click)="excluir(disciplina)">
Excluir
</button>
<button (click)="editar(disciplina)">Editar
</button>
</li>
</ul>
<h2>Cadastrar</h2>
<p>Use este formulário para cadastrar ou editar uma disciplina.</p>
<form #cadastro="ngForm" (submit)="salvar()">
<div>
<label for="nome">Nome</label><br>
<input type="text" id="nome" name="nome" [(ngModel)]="nome" required>
</div>
<div>
<label for="descricao">Descrição</label><br>
<textarea id="descricao" name="descricao" [(ngModel)]="descricao"
cols="70" rows="5"></textarea>
</div>
<button type="submit" [disabled]="!cadastro.valid">Salvar</button>
<button type="reset" (click)="cancelar()">Cancelar</button>
</form>
Para ilustrar a interface completa, a figura a seguir ilustra o processo da interface CRUD
para Disciplina.
Editando dados de uma disciplina
62
Figura 2.10.2 - Relações entre os elementos do Template e do Controller
A figura demonstra as várias relações entre os elementos do Template e do Controller.
Como ela resume os conceitos vistos neste e nos quatro capítulos anteriores, se você
chegou aqui e está um pouco perdido, não se preocupe, pode acontecer. Reserve um
pouco mais de tempo para estudar a figura e o código-fonte e, se necessário, volte nos
capítulos anteriores.
O conjunto de funcionalidades implementadas até aqui demonstram vários recursos do
Angular, mas a interface gráfica não é das melhores. No próximo capítulo vamos aprender
como melhorar o aspecto visual do software.
Editando dados de uma disciplina
63
Interação entre componentes
Objetivos do capítulo:
demonstrar a modularização do código utilizando componentes
demonstrar a integração entre componentes usando input e output
Branches: livro-componentes-lista e livro-componentes-editor.
Uma vez que a quantidade de funcionalidades em um componente começa a aumentar,
prejudicando, por exemplo, a legibilidade e a organização do código-fonte, o componente
torna-se um candidato a passar por um processo de modularização.
O AppComponent , no momento, fornece as funcionalidades de CRUD de disciplinas, ou seja:
Listar disciplinas
Cadastrar disciplinas
Editar disciplinas
Excluir disciplinas
Como alternativa ao fato de haver um só componente realizando todas as essas
funcionalidades vamos utilizar o conceito de componentes do Angular e criar níveis de
interações entre eles.
Criando componentes
O Angular CLI é a ferramenta ideal para criar conteúdo para o software, já vimos isso
quando iniciamos o próprio projeto a partir de uma linha de código. Outro comando
importante é o generate (cujo atalho é g). Ele é usado para criar conteúdo como
componentes, módulos e serviços. Para cada tipo de comando há uma opção, como c,
atalho para component.
Vamos começar pelo componente ListaDeDisciplinas , que implementa a funcionalidade
de listar disciplinas. Para criá-lo use a linha de comando:
ng g c ListaDeDisciplinas --spec false
O comando cria o diretório ./src/app/lista-de-disciplinas e, dentro dele, os arquivos
necessários para o componente, ou seja CSS, Template e Controller.
Interação entre componentes
64
https://github.com/jacksongomesbr/angular-escola/tree/livro-componentes-lista
https://github.com/jacksongomesbr/angular-escola/tree/livro-componentes-editor
Componente lista de disciplinas
O componente ListaDeDisciplinas é um tipo de componente diferente do que vimos até
agora: ele não funciona sozinho. Isso quer dizer que ele precisa ser inserido em outro
componente para funcionar. No nosso contexto, o componente App inclui e utiliza o
componente ListaDeDisciplinas. Vamos chamar o componente App de host (hospedeiro)
e o componente ListaDeDisciplinas de guest (hóspede). Nessa comunicação o host deve
fornecer recursos necessários para o guest funcionar, o que chamamos input (entrada).
Por outro lado, quando o guest notifica o host sempre que fizer qualquer coisa importante,
o que chamamos output (saída ou evento).
Pode ser que não tenha te ocorrido, mas é uma analogia bem interessante. Imagine que
você está hospedando um convidado na sua casa. Você é o host, enquanto seu convidado
é o guest. Sua casa é onde você irá receber seu convidado. O convidado precisa de um
lugar para dormir, então você fornece para ele uma cama (input). Seu convidado te notifica
quer ir embora (output), então você se despede dele e o encaminha até a saída. Pensou
naquele convidado exigente (chato)? Com um componente guest é a mesma coisa: muita
necessidade e muita notificação!
A figura a seguir ilustra o componente ListaDeDisciplinas.
Figura 3.1.1 - Ilustração do componente ListaDeDisciplinas no browser
O componente apresenta uma lista de disciplinas. Cada item da lista contém:
o nome da disciplina
um botão para excluir a disciplina
Interação entre componentes
65
um botão para editar a disciplina
Os dois botões não executam as ações correspondentes (excluir ou editar) porque isso não
é responsabilidade desse componente. O que o componente faz é notificar o host que o
usuário deseja excluir ou editar determinada disciplina. Além disso o componente, ao
apresentar a lista das disciplinas, precisa saber qual disciplina está sendo editada, para que
destaque essa disciplina na lista (perceba, na figura, o ícone do lápis e o fundo da linha com
a cor cinza na disciplina "Língua Portuguesa"). Assim:
input: a lista das disciplinas e a disciplina que está sendo editada; e
output: a disciplina que o usuário deseja excluir ou editar.
O host utiliza (ou recebe) guest, então ele deve fornecer os inputs requeridos. Além disso,
o host também pode realizar alguma ação quando for notificado de que o usuário deseja
realizar uma exclusão ou uma edição de uma disciplina.
A figura a seguir ilustra essa relação entre os dois componentes.
Figura 3.1.2 - Diagrama demonstrando a interação entre os componentes App e
ListaDeDisciplinas
O diagrama apresentado pela figura demonstra as conexões entre os componentes:
App fornece as entradas para ListaDeDisciplinas
os atributos disciplinas e editando de AppComponent são vinculados aos input
disciplinas e editando do ListaDeDisciplinasComponent
ListaDeDisciplinas notifica App quando o usuário desejar excluir ou editar uma
disciplina
o método editar() do AppComponent é executado quando ocorrer o output
onEditar
o método excluir() do AppComponent é executado quando ocorrer o output
Interação entre componentes
66
onExcluir
[info] O diagrama dos componentes
A UML possui um diagrama para representar componentes de um software e as
relações entre eles. Entretanto, por ser uma representação gráfica mais formal, requer
que entradas e saídas sejam interfaces. Aqui adotamos uma simplificação desse
diagrama para suportar o conceito de Input e Output do Angular.
Uma observação importante: os nomes dos atributos das classes (host e guest) não
precisam ser os mesmos.
Antes de utilizar o componente ListaDeDisciplinas no App, vamos ver detalhes da sua
composição. Primeiro, o Controller:
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
@Component({
selector: 'app-lista-de-disciplinas',
...
})
export class ListaDeDisciplinasComponent implements OnInit {
@Input()
disciplinas = null;
@Input()
editando = null;
@Output()
onEditar = new EventEmitter<any>();
@Output()
onExcluir = new EventEmitter<any>();
constructor() {
}
ngOnInit() {
}
excluir(disciplina) {
this.onExcluir.emit(disciplina);
}
editar(disciplina) {
this.onEditar.emit(disciplina);
}
}
Interação entre componentes
67
Atenção para os atributos disciplinas e editando . Ambos são anotados com @Input() ,
o que indica para o Angular tratá-los como input. Os atributos onEditar e onExcluir são
anotados com @Output() , o que indica para o Angular tratá-los como output. Além disso,
perceba também mudanças na linha do import .
Na prática, os atributos onEditar e onExcluir definem eventos, por isso são inicializados
com uma instância de EventEmitter , a classe do Angular pararepresentar eventos. Note,
na instanciação de EventEmitter , que o código define o tipo de dados do evento utilizando
a notação de diamante: <any> . Isso é melhor interpretado assim: um evento pode carregar
consigo alguma informação (dados) e, para isso, é importante indicar o tipo desses dados.
Os métodos excluir(disciplina) e editar(disciplina) são utilizados, respectivamente,
como tratadores de eventos para quando o usuário clicar nos botões correspondentes,
escolhendo excluir ou editar uma disciplina. O importante aqui é que, como estamos
utilizando eventos (output), não queremos que seja responsabilidade desse componente
ListaDeDisciplinas realizar essas tarefas. Pelo contrário, o esperado é notificar o host de
que isso aconteceu. Para indicar para o host que uma disciplina deve ser excluída é usada
a chamada para o método this.onExcluir.emit(disciplina) . Isso fará um "disparo" do
evento, notificando o host de que ele ocorreu, e incluirá, como dado do evento, a disciplina
em questão (o objeto disciplina ). Da mesma forma, para notificar o host que deve editar
uma disciplina é usada uma chamada para o método this.onEditar.emit(disciplina) .
O Template do ListaDeDisciplinas contém:
Interação entre componentes
68
<table class="table table-hover table-bordered">
<thead class="thead-light">
<tr>
<th>Disciplina</th>
<th width="200">Ações</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let disciplina of disciplinas"
[class.table-active]="editando == disciplina">
<td>
<i *ngIf="editando == disciplina"
class="fa fa-pencil-square"></i>
{{disciplina.nome}}
</td>
<td>
<button class="btn btn-sm btn-danger"
(click)="excluir(disciplina)">
<i class="fa fa-trash"></i> Excluir
</button>
<button class="btn btn-sm btn-primary"
(click)="editar(disciplina)">
<i class="fa fa-pencil-square"></i> Editar
</button>
</td>
</tr>
</tbody>
</table>
Percebeu semelhança com o Template do capítulo anterior? Isso não é coincidência. Foi
realmente isso que aconteceu. Tanto é que a melhor dica para se trabalhar com múltiplos
componentes em Angular, pelo menos enquanto você for um dev iniciante, é usar apenas
um componente e implementar nele todas as funcionalidades desejadas. Depois, a tarefa é
"recortar" partes desse componente e usar em outros componentes.
A forma pela qual o host incorpora o guest e informa o vínculo com input e output é por
meio do Template. Veja o trecho a seguir, do Template do componente App:
<app-lista-de-disciplinas
[disciplinas]="disciplinas"
[editando]="editando"
(onEditar)="editar($event)"
(onExcluir)="excluir($event)">
</app-lista-de-disciplinas>
Primeiro, o elemento app-lista-de-disciplinas é utilizado para indicar para o Angular que
nesse local deve ser apresentado o componente ListaDeDisciplinas. Isso acontece por
causa do selector do componente ListaDeDisciplinas (veja o Controller desse
Interação entre componentes
69
componente).
Segundo, a sintaxe que tem um atributo HTML com nome entre colchetes você já conhece.
É a mesma que já utilizamos para definir, por exemplo, a classe CSS de um elemento do
HTML com base em uma condição (ex: o fundo da linha da tabela na cor cinza quando a
disciplina está sendo editada). Então o trecho [disciplinas]="disciplinas" indica o uso de
Data Binding: o input chamado disciplinas está vinculado ao atributo disciplinas . Da
mesma forma o trecho [editando]="editando" usa Data binding para vincular o input
editando ao atributo editando .
Por fim, a sintaxe que tem um atributo HTML com nome entre parênteses você também já
conhece. É a mesma que já utilizamos para tratar eventos (ex: excluir uma disciplina
quando o usuário clicar em um botão associado a ela). Então o trecho
(onExcluir)="excluir($event)" indica o uso de um tratador de eventos: o método
excluir() deve ser chamado passando $event como parâmetro quando ocorrer o evento
onExcluir do componente ListaDeDisciplinas. É importante observar que $event é uma
forma de acessar os dados do evento quando disparado no componente
ListaDeDisciplinas. Nesse caso, do evento onEditar , o dado é a disciplina que deve ser
excluída. O mesmo comportamento acontece para o evento onEditar .
A figura a seguir procura resumir todas as informações apresentadas até aqui e procura
demonstrar a interação entre os componentes App e ListaDeDisciplinas.
Figura 3.1.3 - Interações entre os componentes App e ListaDeDisciplinas
Interação entre componentes
70
O repositório livro-componentes-editor ainda fornece mais um nível de criação de
componentes, demonstrando o código para o componente EditorDeDisciplina, que fornece
um formulário para cadastro e para edição de uma disciplina. Fica como exercício para
fixação da aprendizagem o estudo do código desse repositório.
[info] Resumo do capítulo:
Utilizamos Angular CLI para criar um componente
Um host é um componente que utiliza ou inclui um outro, chamado de guest
Um guest não funciona sem ser utilizado em um host
O conceito de Input é utilizado para o host fornecer entrada para o guest
O conceito de Output é utilizado para o guest notificar o host de que algo (um
evento) aconteceu
A interação entre componentes é mais um recurso que usa Data binding para
vincular entradas do guest a atributos do host
Interação entre componentes
71
Serviços
Objetivos do capítulo:
demonstrar a modularização de código utilizando serviços
Branch: livro-services
Conforme a Arquitetura do Angular a utilização de Serviços tem o propósito de organizar o
projeto de software Angular, isolando lógica de negócio e separando-a dos Controllers. Não
é possível afirmar que seja obrigatório utilizar serviços, mas é muito desejável.
Na prática não há diferença para o usuário porque, provavelmente, utilizar serviços não
afetará diretamente o comportamento da interface. Assim, os benefícios ficam por conta da
melhor organização do projeto e da Engenharia de Software para o projeto que está sendo
desenvolvido.
Um serviço é uma classe que pode ser utilizada por outros componentes do projeto. Dois
detalhes importantes são: o que diferencia um serviço de outro tipo de componente e como
um componente utiliza um serviço. Veremos isso a seguir.
Até o momento a lógica do CRUD de disciplinas está toda nos Controllers dos
componentes. Isso não é uma boa prática, porque é indicado que a lógica dos Controllers
seja voltada para gerenciar o comportamento da interface, ou seja, para controlar a
interação entre o usuário e o software. Vamos começar criando o serviço DisciplinasService
usando Angular CLI e o comando generate (abreviado por g) e a opção service (abreviada
por s):
ng g s Disciplinas --spec false --module App
A linha de comando cria o arquivo ./src/app/disciplinas.service.ts (a opção --spec false
é utilizada para não criar um arquivo de testes, que é o padrão). A opção --module App é
utilizada para que o módulo App seja modificado para prover o serviço. Um módulo precisa
prover ou fornecer um serviço para que ele possa ser utilizado pelos seus componentes.
Para isso ocorre uma alteração na declaração do módulo App, como mostra o trecho de
código a seguir:
Serviços
72
https://github.com/jacksongomesbr/angular-escola/tree/livro-services
...
import { DisciplinasService } from './disciplinas.service';
@NgModule({
declarations: [
...
],
imports: [
...
],
providers: [DisciplinasService],
bootstrap: [AppComponent]
})
export class AppModule {
}
O atributo providers está informando para os demais componentes do módulo App que o
serviço DisciplinasService está disponível para uso.
O Angular CLI cria um serviço operacional, mas não funcional, ou seja, vazio. Entretanto, é
bom dar uma olhada no código:
import { Injectable } from '@angular/core';
@Injectable()
export class DisciplinasService {
constructor() { }
}
Novamente, um serviço é uma classe.A diferença está na anotação @Injectable() . É ela
que instrui o Angular a considerar a classe DisciplinasService como um serviço -- pois é,
não há uma anotação com nome parecido com @Service() =.
Então, vamos adicionar funcionalidades nesse serviço:
retornar a lista de disciplinas
salvar uma disciplina (cadastrar ou excluir)
excluir uma disciplina
encontrar uma disciplina
Na verdade, essas funcionalidades já estão disponíveis no Controller do AppComponent ,
então o trabalho agora é realocar código. Dessa forma, o código do DisciplinasServices
fica mais ou menos assim:
import {Injectable} from '@angular/core';
import {Disciplina} from './disciplina.model';
Serviços
73
@Injectable()
export class DisciplinasService {
private disciplinas = null;
private novo_id = 1;
constructor() {
this.disciplinas = [
new Disciplina(1, 'Língua Portuguesa', 'O ...'),
...
];
}
todas(): Array<Disciplina> {
return this.disciplinas;
}
salvar(id: number, nome: string, descricao: string): Disciplina {
if (id) {
let d = this.encontrar(id);
if (d) {
d.nome = nome;
d.descricao = descricao;
} else {
throw new Error('Não foi possível encontrar a disciplina');
}
return d;
} else {
const d = new Disciplina(this.novo_id, nome, descricao);
this.disciplinas.push(d);
this.novo_id++;
return d;
}
}
excluir(disciplina: number | Disciplina) {
let d = null;
if (typeof(disciplina) === 'number') {
d = this.encontrar(disciplina);
} else {
d = this.encontrar(disciplina.id);
}
if (d) {
const i = this.disciplinas.indexOf(d);
this.disciplinas.splice(i, 1);
} else {
throw new Error('Não foi possível encontrar a disciplina');
}
}
encontrar(arg: number | string) {
let d = null;
if (typeof(arg) === 'number') {
d = this.disciplinas.filter(d => d.id === arg);
Serviços
74
} else {
d = this.disciplinas.filter(d => d.nome === arg);
}
if (d != null && d.length > 0) {
return d[0];
} else {
return null;
}
}
}
O método todas() retorna todas as disciplinas.
O método encontrar() recebe o id da disciplina ou o seu nome, procura o array
disciplinas e, se encontrar, retorna a disciplina correspondente. Caso contrário retorna
null para indicar que a disciplina não foi encontrada. Note a expressão usada para definir
o tipo do parâmetro arg : number | string . Isso significa que o método aceita um
parâmetro do tipo number ou string . Note também a utilização do método filter() para
aplicar um predicado (representado por uma função seta) para encontrar um item do array.
A lógica dos métodos salvar() e excluir() é semelhante à utilizada anteriormente no
AppComponente , com a diferença de que não há interação com o usuário. É importante
ressaltar também que esses métodos estão disparando exceções (veja as linhas com throw
new Error(...) ) que devem ser devidamente tratadas nos componentes que utilizarem esse
serviço.
Já no Controller do AppComponent a mudança começa no método construtor:
constructor(private disciplinasService: DisciplinasService) {
this.disciplinas = this.disciplinasService.todas();
}
Essa sintaxe private disciplinasService: DisciplinasService utiliza um recurso importante
do Angular chamado Dependency Injection (DI). Por meio de DI o Angular cria
automaticamente uma instância de DisciplinasService e a atribui ao atributo privado
disciplinasService , tornando o serviço disponível no componente. Com isso o código
desse Controller fica como o seguinte:
Serviços
75
...
import {DisciplinasService} from "./disciplinas.service";
...
export class AppComponent {
editando = {id: 0, nome: '', descricao: ''};
...
disciplinas = null;
constructor(private disciplinasService: DisciplinasService) {
this.disciplinas = this.disciplinasService.todas();
}
salvar() {
try {
const d = this.disciplinasService.salvar(this.editando.id,
this.editando.nome, this.editando.descricao);
this.salvar_ok = true;
this.editando = {id: 0, nome: '', descricao: ''};
} catch (e) {
this.salvar_erro = true;
}
}
excluir(disciplina) {
if (this.editando === disciplina) {
alert('Você não pode excluir uma disciplina que está editando');
} else {
if (confirm('Tem certeza que deseja excluir a disciplina "'
+ disciplina.nome + '"?')) {
try {
this.disciplinasService.excluir(disciplina);
this.excluir_ok = true;
this.redefinir();
} catch (e) {
this.excluir_erro = true;
}
}
}
}
...
}
Perceba como a lógica da interface foi mantida e o código não possui a lógica de manipular
o array de disciplinas (consultar, inserir, excluir e encontrar elementos), já que isso fica a
cargo do serviço DisciplinasService . Assim, o componente AppComponent utiliza o serviço
DisciplinasService e vimos como utilizar esse curso para organizar melhor o código e
utilizar os padrões de arquitetura do Angular.
Serviços
76
Como o código-fonte do software está crescendo, tanto em termos de volume quanto de
arquivos, é interessante adotar uma abordagem que auxilia ainda mais na organização de
código: diagramas da UML. A figura a seguir apresenta o diagrama de classes do software
até então.
Figura 3.2.1 - Diagrama de classes do software
O diagrama de classes apresentado pela figura demonstra relações entre alguns elementos
do software até então, principalmente com foco nos controllers dos componentes, no
serviço DisciplinasService e nas relações (usa) entre eles.
Serviços
77
Acessando dados externos de forma
assíncrona
[info] Objetivos do capítulo:
demonstrar a representação de dados em arquivos JSON
demonstrar o uso do HttpClient para acessar dados externos (arquivo JSON)
apresentar o padrão Observer e explicar seu funcionamento
apresentar a comunicação entre componentes do software usando diagrama de
sequência da UML
Branch: livro-dados-externos-json
Até o capítulo anterior (Serviços) a estrutura do software já começa a ficar mais modular.
Para prosseguir nessa direção uma modificação importante é trabalhar com fontes de
dados. Por enquanto, vamos utilizar um mecanismo simples, mas muito eficiente.
O serviço DisciplinasService possui um atributo disciplinas , que contém um array de
objetos. Embora o software esteja funcional o array de objetos é definido diretamente no
código-fonte, o que pode ser melhorado. A prática mais adequada é separar as coisas: de
um lado o software, do outro a fonte de dados.
Uma fonte de dados é um conceito abstrato que pode ser representado por um arquivo de
texto, JSON ou um banco de dados, por exemplo. Aqui, usaremos um arquivo em formato
JSON.
O arquivo ./src/assets/dados/disciplinas.json contém a lista das disciplinas:
[
{
"id": 1,
"nome": "Língua Portuguesa",
"descricao": "..."
},
{
"id": 2,
"nome": "Inglês",
"descricao": "..."
},
...
]
Acessando dados externos de forma assíncrona
78
https://github.com/jacksongomesbr/angular-escola/tree/livro-dados-externos-json
Para acessar os dados de arquivos JSON é necessário realizar requisições HTTP para um
servidor web. Se você estiver executando o projeto em modo de desenvolvimento, o
servidor já disponibilizado pelo Angular CLI para desenvolvimento será utilizado tanto para
servir o conteúdo do software quanto fornecer acesso ao arquivo JSON. Para isso o
Angular disponibiliza o módulo HttpClientModule , que precisa, primeiro, ser importado no
root module:
...
import {HttpClientModule} from '@angular/common/http';
@NgModule({
declarations: [
...
],
imports: [
NgbModule.forRoot(),
BrowserModule,
HttpClientModule,
FormsModule
],
...
})
export class AppModule {
}
Depois,o serviço DisciplinasService precisa ser modificado:
...
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class DisciplinasService {
private disciplinas = null;
private novo_id = 1;
constructor(private http: HttpClient) {
}
carregarDados(callback) {
this.http.get('./assets/dados/disciplinas.json')
.subscribe(disciplinas => this.disciplinas = disciplinas)
.add(callback);
}
...
}
Acessando dados externos de forma assíncrona
79
A primeira modificação no código do serviço é a injeção de dependência de um
HttpClient , uma classe do módulo HttpClientModule que fornece métodos apropriados
para realizar requisições HTTP assíncronas. A classe HttpClient fornece o método
get() , que é utilizado para realizar uma requisição HTTP GET. O parâmetro informado
para o método get() é uma URL que, nesse caso, corresponde ao caminho para o arquivo
disciplinas.json .
A classe HttpClient é usada efetivamente no método carregarDados() , que aceita como
parâmetro uma função callback que deverá ser executada quando o carregamento dos
dados for concluído. Esse comportamento é uma estratégia para carregar os dados de uma
vez, como um array de disciplinas, mantendo uma referência para esse array no atributo
DisciplinasSservice.disciplinas e também no atributo AppComponent.disciplinas . Isso
acontece porque a forma de acesso à lista de disciplinas, até o momento, não permite
alterar seu conteúdo de forma persistente, ou seja, não salva os dados.
Antes de prosseguirmos com a leitura do código, é importante detalhar o conceito de
requisição assíncrona. A figura a seguir ilustra esse processo.
Figura 3.3.1 - Ilustração do processo de requisição assíncrona
A figura demonstra que o Cliente faz uma requisição HTTP para o Servidor (usando o verbo
GET do HTTP) que busca pelo arquivo solicitado. Conforme a situação o Servidor retorna
uma de duas respostas possíveis para o Cliente:
código 200: o arquivo foi encontrado, então retorna o conteúdo dele
código 404: o arquivo não foi encontrador, então não há conteúdo no retorno
Acessando dados externos de forma assíncrona
80
De qualquer forma, um comportamento característico desse cenário acontece: quando o
Cliente faz a requisição ele continua em funcionamento enquanto aguarda uma resposta do
Servidor. Quando a resposta chega o Cliente é notificado que isso aconteceu e executa
uma ação específica para cada tipo de retorno (conforme o código de retorno 200 ou 404,
por exemplo). É por isso que a requisição é assícrona.
Em termos de código o método get() da classe HttpClient não realiza a requisição. Ele
cria um objeto do tipo Observable (do pacote rxjs ) que implementa um padrão de projeto
chamado Observer, que é ideal para essa situação das requisições assíncronas. Por causa
desse padrão de projeto, vamos a uma analogia demonstada pela figura a seguir.
Figura 3.3.2 - Ilustração do processo de assinatura de uma revista
A figura ilustra a comunicação entre Cliente e Editora. Depois de realizar a assinatura o
cliente fica aguardando a Editora publicar uma nova edição da revista e enviar um exemplar
para o Cliente, observando esse processo. Enquanto isso acontece o Cliente não fica
parado, mas vai realizando seus afazeres. Quando um exemplar da nova edição chega,
então o Cliente inicia a sua leitura.
O padrão Observer tem dois elementos importantes: o publisher (no caso, a Editora) e o
subscriber (no caso, o Cliente). Um terceiro elemento é importante é a revista, que
representa o dado entregue pela Editora para o Cliente.
No contexto do código o método get() cria uma instância de Observable . Para realizar
uma requisição e realizar uma ação quando a resposta chega do servidor é utilizado o
método subscribe() . Por sua vez o método subscribe() pode receber dois parâmetros:
Acessando dados externos de forma assíncrona
81
o primeiro parâmetro é uma função callback executada quando a requisição for
bem sucedida. Nesse caso, o retorno da requisição é convertido para JSON e tratado
no código como um objeto, representado pelo parâmetro da função callback
o segundo parâmetro é uma função callback executada quando a requisição não
for bem sucedida. Nesse caso o retorno da requisição (se houver) também é
convertido para JSON e tratado no código como um objeto, também representado pelo
parâmetro da função callback.
No caso do código do serviço DisciplinasService apenas o primeiro parâmetro está sendo
utilizado, o que ilustrado pela figura a seguir.
Figura 3.3.3 - Ilustração da composição de uma função callback para o método subscribe\(\)
A sintaxe utilizada é conhecida como arrow function (função seta) e, por ser uma função,
tem duas partes:
parâmetros; e
corpo.
Se a lista de parâmetros for vazia, pode-se usar apenas () .
Voltando ao código do serviço DisciplinasService a função callback de sucesso para a
chamada de subscribe() atribui o parâmetro disciplinas (a lista de disciplinas retornada
pelo servidor já em formato de objetos) para o atributo da classe de mesmo nome.
Para finalizar a leitura do código do DisciplinasService há mais um recurso importante
sendo utilizado. O método carregarDados() recebe como parâmetro uma função callback.
O método cria uma instância de Observable e chama o método subscribe() . Como o
objetivo desse método é executar a função callback informada como parâmetro quando a
requisição HTTP for concluída e os dados estiverem disponíveis, é feita uma chamada para
a função add() , informando como parâmetro a função callback.
Para usar o serviço DisciplinasService o constructor() do Controller AppComponent
comporta-se da seguinte forma:
Acessando dados externos de forma assíncrona
82
constructor(private disciplinasService: DisciplinasService) {
this.disciplinasService.carregarDados(
() => this.disciplinas = this.disciplinasService.todas()
);
}
Perceba que a chamada para carregarDados() informa uma arrow function como
parâmetro (a callback).
Para finalizar esse capítulo, a figura a seguir resume e ilustra os conceitos apresentados,
indicando a sequência de passos da comunicação entre os componentes.
Figura 3.3.4 - Diagrama de sequência do software, demonstrando a interação entre
AppComponent, DisciplinasService e Servidor HTTP
Se necessário analise a sequência do diagrama mais de uma vez, acompanhado do código-
fonte.
Acessando dados externos de forma assíncrona
83
Acessando dados de uma API HTTP
[info] Objetivos do capítulo:
reforçar o conceito de requisição assícrona utilizando a classe HttpClient
demonstrar a utilização da classe HttpClient para acessar dados de uma API
HTTP REST
demonstrar como lidar com situações de sucesso e erro ao lidar com requisições
assíncronas
Branch: livro-dados-externos-api-http-rest
O capítulo Acessando dados externos de forma assíncrona apresentou como utilizar
recursos do Angular para acessar dados de um arquivo JSON. Entretanto, a limitação
daquela abordagem, embora seja suficiente para várias situações, precisa de uma
alternativa mais completa, que permita a persistência dos dados. Uma solução para essa
questão é utilizar uma API HTTP. Vamos utilizar uma API HTTP REST disponível no
repositório angular-escola-api. Este capítulo não apresenta detalhes de como fazer essa
API funcionar, mas vez ou outras algumas informações serão apresentadas.
Iniciando o servidor web da API
Depois de clonar o repositório da API execute o comando:
npm start
Após alguns instantes o servidor estará disponível para aceitar requisições. Se quiser testar
a API diretamente pelo browser acesse o endereço da API, indicado na mensagem de
saída do comando anterior.
Modificando o DisciplinasService
Como você já sabe o serviço DisciplinasService do capítulo anterior está fazendo
requisições para o servidor web de desenvolvimento em busca do arquivo JSON que
contém a lista das disciplinas. Para consultar a API a primeira alteração está na estrutura doserviço:
remover todos os atributos
Acessando dados de uma API HTTP
84
https://github.com/jacksongomesbr/angular-escola/tree/livro-dados-externos-api-http-rest
https://github.com/jacksongomesbr/angular-escola-api
adicionar o atributo API_URL com o valor "http://localhost:3000" (que é o endereço
padrão do servidor web da API)
remover o método carregarDados()
alterar o código dos métodos todas() , salvar() , excluir() e encontrar() .
Assim, o serviço DisciplinasService fica dessa maneira:
...
@Injectable()
export class DisciplinasService {
API_URL = 'http://localhost:3000';
constructor(private http: HttpClient) { }
todas() {
return this.http.get(this.API_URL + '/disciplinas');
}
salvar(id: number, nome: string, descricao: string) {
const disciplina = {nome: nome, descricao: descricao};
if (id) {
return this.http.patch(this.API_URL + '/disciplinas/' + id, disciplina);
} else {
return this.http.post(this.API_URL + '/disciplinas', disciplina);
}
}
excluir(disciplina: number | Disciplina) {
let id;
if (typeof(disciplina) === 'number') {
id = disciplina;
} else {
id = disciplina.id;
}
return this.http.delete(this.API_URL + '/disciplinas/' + id);
}
encontrar(arg: number) {
return this.http.get(this.API_URL + '/disciplinas/' + arg);
}
}
O método todas() utiliza o método get() do HttpClient para realizar uma requisição
GET para a URL /disciplinas (a partir da URL da API) para retornar a lista das disciplinas.
A API retorna um array de objetos com os atributos: id , codigo , nome e descricao . Da
mesma forma como antes (no antigo método carregarDados() ) os dados são convertidos
para objetos automaticamente quando a requisição HTTP for realizada -- lembre-se que é
preciso chamar o método subscribe() para isso.
Acessando dados de uma API HTTP
85
O método salvar() recebe os dados de uma disciplina como parâmetro ( id , nome ,
descricao ), define um objeto chamado disciplina com os valores dos parâmetros e
checa o valor do parâmetro id para saber se é necessário:
alterar: nesse caso o id possui algum valor e é feita uma requisição PATCH para a
URL da disciplina na API, por exemplo /disciplinas/1 , por meio do método patch()
da classe HttpClient ; ou
cadastrar: nesse caso o id não possui valor (ex: null ) e é feita uma requisição
POST para a URL /disciplinas da API por meio do método post() da classe
HttpClient .
Perceba que nos dois casos o segundo parâmetro da chamada dos métodos patch() e
post() é o objeto disciplina . Isso é necessário porque a API espera que os dados
estejam em formato JSON, o que também é feito automaticamente pelo Angular (converte o
objeto disciplina para sua representação em JSON ao enviar os dados para a API). Além
disso, da mesma forma como acontece com o método get() da classe HttpClient , a
requisição HTTP só acontece, efetivamente, quando é chamado o método subscribe() .
O método excluir() recebe o parâmetro disciplina , que pode ser um número (o
identificador da disciplina) ou o objeto que representa a disciplina. Com base no tipo do
parâmetro o código obtém o identificador da disciplina e o utiliza para fazer uma requisição
DELETE para a URL da API que representa a disciplina (ex: /disciplinas/1 ). Isso é feito
por meio de uma chamada para o método delete() da classe HttpClient .
Por último, o método encontrar() recebe o identificador da disciplina como parâmetro e faz
uma requisição GET para a URL da API que representa a disciplina por meio do método
get() da classe HttpClient .
Modificando o AppComponent
As alterações mais expressivas acontecem no Controller do AppComponent para usar o
serviço DisciplinasService . Primeiro, o constructor() chama o novo método
atualizarLista() :
Acessando dados de uma API HTTP
86
constructor(private disciplinasService: DisciplinasService) {
this.atualizarLista();
}
atualizarLista() {
this.disciplinasService.todas()
.subscribe(disciplinas => {
this.disciplinas = disciplinas;
this.status_lista = true;
}, () => this.status_lista = false);
}
O método atualizarLista() chama o método todas() do DisciplinasService . Perceba
que, porque o retorno do método é um Observable , é possível chamar o método
subscribe() logo em seguida. Como de costume, uma figura para explicar isso em partes:
Figura 3.4.1 - Ilustração da estrutura de callback de sucesso e erro
A figura destaca, na cor verde, o código da callback de sucesso. Lembre-se que a callback
de sucesso executa se a requisição para a API for bem sucedida (código de retorno igual
200). Nesse caso a arrow function tem o parâmetro disciplinas e o corpo da função faz:
atribui disciplinas para this.disciplinas (a lista das disciplinas, que o Template
utiliza)
atribui true para this.status_lista (um valor de controle utilizado no Template)
Ainda na figura, a cor vermelha destaca a callback de erro. A callback de erro executa se a
requisição para a API não for bem sucedida (código de retorno diferente de 200, como 404
ou 500). A arrow function não tem parâmetro e seu corpo atribui false para
Acessando dados de uma API HTTP
87
this.status_lista .
O método salvar() chama o método salvar() do DisciplinasService :
salvar() {
this.disciplinasService.salvar(this.editando.id,
this.editando.nome, this.editando.descricao)
.subscribe(disciplina => {
this.redefinir();
this.salvar_ok = true;
this.atualizarLista();
},
() => {
this.redefinir();
this.salvar_erro = true;
}
);
}
Nesse caso a callback de sucesso, que indica que os dados foram salvos na API, faz com
que a tela apresente uma mensagem de sucesso e a lista de disciplinas é atualizada (para
mostrar os novos dados das disciplinas). Note também que a callback recebe o parâmetro
disciplina , que não é utilizado, mas que é retornado pela API contendo os dados do
objeto. No caso da API que estamos utilizando, se for feita uma requisição POST o objeto
retornado pela API contém um atributo id cujo valor é gerado automaticamente, de forma
incremental (lembra que já precisamos controlar isso manualmente?). No caso da callback
de erro, o código faz com que a tela apresente uma mensagem de erro.
Os demais métodos, excluir() e editar() , utilizam, respectivamente, os métodos
excluir() e encontrar() da classe DisciplinasService . Fica como exercício interpretar o
código.
A execução do software no browser não é muito diferente do capítulo anterior. Entretanto,
como estamos utilizando uma API HTTP REST, é importante tomar certos cuidados. Por
exemplo, se, por algum motivo, não for possível acessar a API, é interessante apresentar
uma mensagem de erro na tela. A figura a seguir ilustra essa situação.
Acessando dados de uma API HTTP
88
Figura 3.4.2 - Execução do software no browser demonstrando mensagem de erro de
conexão com a API HTTP REST
Caso contrário, se a API estiver acessível e tudo estiver ocorrendo como esperado, a
interface continua mostrando os dados e permitindo interação com o usuário normalmente.
Para finalizar o capítulo, um diagrama de sequência demonstrando a interação entre
AppComponent , DisciplinasService e o Servidor HTTP da API quando é executado o
método atualizarLista() do AppComponent .
Acessando dados de uma API HTTP
89
Figura 3.4.3 - Diagrama de sequência demonstrando a interação entre AppComponent,
DisciplinasService e Servidor HTTP quando é chamado o método atualizarLista() do
AppComponent
O diagrama de sequência demonstra as interações desde a chamada do método todas\
(\) do DisciplinasService até o tratamento do retorno do Servidor HTTP (ou erro).
[info] Resumo do capítulo:
A classe HttpClient é muito útil para acessar dados via requisições HTTP
A classe HttpClient pode ser utilizada para comunicação com uma API HTTP
REST
A utilização de serviçospara organização do código é um recurso para melhoria
da arquitetura do software
Acessando dados de uma API HTTP
90
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.
Rotas
91
https://github.com/jacksongomesbr/angular-escola/tree/livro-rotas
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/7
899
Há informações no URL que pertencem à notícia e mudam de uma notíca para outra:
categoria: politica
Rotas
92
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.
Rotas
93
Figura 3.5.1 - Estrutura de classes do software
Como mostra a figura a estrutura do software contém:
um módulo: AppModule
quatro componentes: AppComponent , HomeComponent , ListaDeDisciplinasComponent e
EditorDeDisciplinasComponent
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:
Rotas
94
AppComponent não tem a lógica para gerenciar dados de disciplinas
ListaDeDisciplinasComponent não tem input ou output, pois interage diretamente com o
DisciplinasService
EditorDeDisciplinaComponent não tem input ou ouptut, interage diretamente com o
DisciplinasService 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 :
Rotas
95
...
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; e
component : 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 .
Rotas
96
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.
Figura 3.5.2 - Processo de interpretação da URL em busca em uma rota e do seu
componente
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
Rotas
97
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 (o AppComponent é o shell
component)
onde está o elemento router-outlet no Template do AppComponent
A figura a seguir ilustra esse processo, considerando que a URL no browser é
http://localhost:4200 .
Figura 3.5.3 - Utilização do shell component e combinação do seu Template com o
Template do componente associado à rota
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.
Rotas
98
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étodo get() é o
nome do parâmetro de rota desejado
tratar o valor do parâmetro de rota: o valor retornado por get() é do tipo string .
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étodo
disciplinasService.encontrar() , passando como parâmetro o valor do parâmetro de
rota convertido para o tipo number , usando a função parseInt() , 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
Rotas
99
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)
Rotas
100
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 arquiteturado
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):
1. 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;
2. definir rotas: depois, defina as rotas (utilizando, por exemplo, o root module, como
visto neste capítulo);
3. 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
4. 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
classe Router
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
Rotas
101
Feature-modules
[info] Objetivos do capítulo:
demonstrar a modularização do software utilizando módulos
criando componentes em módulos
importando módulos
descrevendo rotas em módulos
Branch: livro-modulos-admin-publico-shared
Todos os capítulos anteriores estão convergindo para um ponto: utilizar recursos do Angular
para melhorar a arquitetura e estrutura do software. A figura a seguir dá uma ideia mais
visual disso.
Figura 3.6.1 - Arquitetura do software demonstrando interações entre módulos e API
O funcionamento esperado é o seguinte:
a interface gráfica é representada pelo arquivo src/index.html
AppModule , que é o root module, é apresentado na interface gráfica
AppModule importa os módulos AdminModule e PublicoModule .
AdminModule e PublicoModule importam o SharedModule e comunicam-se com a API
HTTP REST.
Feature-modules
102
https://github.com/jacksongomesbr/angular-escola/tree/livro-modulos-admin-publico-shared
Na prática, o AppModule funciona como um concentrador dos demais módulos, já que ele
tem poucas funcionalidades específicas, por isso os demais módulos, que fornecem
funcionalidades, são chamados feature modules:
AdminModule é responsável pelas funcionalidades administrativas (requerem acesso
por login e senha);
PublicoModule contém funcionalidades que não requererem acesso por login e senha.
SharedModule contém funcionalidades que são utilizadas por outros módulos.
Na sequência o texto demonstra os elementos dessa arquitetura.
Estrutura do software
Apresentar uma figura ilustrando o diagrama de classes do software até aqui seria inútil por
causa da quantidade de arquivos e artefatos do software até então. Entretanto, para ter
uma ideia do tamanho e da complexidade do software como criado neste capítulo, as
tabelas a seguir apresentam a relação de componentes em cada feature module e uma
breve descrição de cada um deles.
Módulo Admin
Feature-modules
103
Componente Descrição
AdminComponent o shell component do módulo Admin
CadastroDeDisciplinaComponent
fornece um formulário para cadastro de uma
disciplina
CadastroDeTurmaComponent fornece um formulário para cadastro de uma turma
DisciplinaComponent
fornece a página inicial de uma disciplina, com
dados para leitura ou consulta
HomeComponent fornece a página inicial do módulo Admin
ListaDeDisciplinasComponent
fornece uma lista das disciplinas e dá acesso a
outras funcionalidades: cadastrar, excluir e editar
ListaDeTurmasComponent
fornece uma lista das turmas e dá acesso a outras
funcionalidades: cadastrar, excluir e editar
PaginaNaoEncontradaComponent
fornece uma página para ser apresentada quando
uma URL não combinar com alguma das rotas do
módulo Admin
TurmaComponent
fornece a página inicial de uma turma, com dados
para leitura ou consulta
AdminModule o módulo, em si
AdminRoutingModule o módulo de rotas
Disciplina o model de disciplina
DisciplinasService
o serviço com a lógica de negócio para o
gerenciamento de disciplinas
Turma o model de turma
TurmasService
o serviço com a lógica de negócio para o
gerenciamento de turmas
Módulo Público
Componente Descrição
LoginComponent fornece um formulário para autenticação e acesso ao sistema
PublicoComponent o shell component do módulo Publico
PublicoModule o módulo, em si
PublicoRoutingModule o módulo de rotas
Módulo Shared
Feature-modules
104
Component Descrição
AuthService o serviço com a lógica para autenticação do usuário
SharedModule o módulo, em si
Estrutura de Navegação
Conforme o software aumenta na quantidade de componentes e funcionalidades torna-se
interessante uma visão geral da navegação -- até mesmo porque estamos utilizando rotas.
A figura a seguir ilustra a navegação entre as telas (ou páginas) do software.
Figura 3.6.2 - Estrutura do site na forma de um mapa de navegação
Essa figura permite compreender que a primeira tela acessada é a tela de login. A partir
dela o usuário tem acesso à tela home e às demais telas, como disciplinas e turmas. A
partir dessas telas outras podem ser acessdas, como a página da disciplina ou a página da
turma. As setas são bidirecionais para indicar os caminhos são de ida e volta.
Criando módulos
Para criar os elementos da arquitetura vamos utilizar o Angular CLI. Para começar, vamos
criar os feature modules. Para fazer isso execute a linha de comando a seguir.
ng g m Admin --routing true
O comando cria o módulo Admin , na pasta src/app/admin , e os arquivos:
src/app/admin/admin.module.ts : representa o módulo em si
Feature-modules
105
src/app/admin/admin-routing.module.ts : representa o módulo de rotas
O capítulo anterior demonstrou como trabalhar com o recurso de rotas no Angular.
Entretanto o capítulo utilizou o formato de descrever as rotas no próprio módulo AppModule .
Aqui neste capítulo, como há mais módulos, cada módulo possui, também, um módulo de
rotas. Um módulo de rotas é um módulo que tem uma única responsabilidade: definir as
rotas do módulo. Esse módulo será importado no feature module para que o Angular
identifique corretamente as rotas e os seus componentes associados. O comando anterior
especifica que deve ser criado um módulo de rotas por meio da opção --routing true .
Criando componentes nos módulos
Para criar componentes em um módulo utilizamos o Angular CLI, usando a linha de
comando a seguir:
ng g m Admin/Admin -m Admin --spec false
O comando cria o componente Admin , na pasta src/app/admin/admin e os arquivos do
componente. Usar o Admin/Admin é uma forma de indicar para o Angular CLI que o
componente Admin pertence ao módulo Admin . A opção -m é uma forma de garantir isso
ainda mais. Além disso o comando também modifica o AdminModule , inserindo o
AdminComponent no array declarations .
No padrão adotado neste livro todo feature module possui um componente de mesmo
nome que é usado como shell component -- porque continuamos a usar o recurso de
rotas.
Os demais componentes são criados de forma semelhante. Por exemplo, a criação do
componente CadastroDeDisciplina:
ng g m Admin/CadastroDeDisciplina -m Admin --spec false
A linha de comando cria o componente CadastroDeDisciplina na pasta
src/app/admin/cadastro-de-disciplina e modifica o AppModule para incluir esse
componente no array declarations .
Definindo rotas
Feature-modules
106
Com a utilização de feature moduleso recurso de rotas ganha uma utilidade ainda mais
marcante e evidente. O fundamento adotado pelo Angular é que cada feature module pode
possuir seu módulo de rotas. Posteriormente, o root module poderá importar o feature
module que desejar.
O módulo de rotas para o módulo Admin , chamado AdminRouting , contém um conteúdo
semelhante ao seguinte (omitidas linhas com import ):
...
const routes: Routes = [
{
path: 'admin', component: AdminComponent, children: [
{path: 'disciplinas', component: ListaDeDisciplinasComponent},
{path: 'disciplinas/:id', component: DisciplinaComponent},
{path: 'disciplinas/:id/novo', component: CadastroDeDisciplinaComponent},
{path: 'disciplinas/:id/editar', component: CadastroDeDisciplinaComponent},
{path: 'cadastrar-turma', component: CadastroDeTurmaComponent},
{path: 'turmas', component: ListaDeTurmasComponent},
{path: 'turmas/:id', component: TurmaComponent},
{path: '', component: HomeComponent},
{path: '**', component: PaginaNaoEncontradaComponent}
]}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule {
}
Aqui o array routes ganha uma característica diferenciada: o primeiro elemento, cujo
atributo path tem valor 'admin' , está relacionado ao componente AdminComponent e
possui o atributo children , que é um array de rotas. O recurso que permite definir esses
tipos de rotas é chamado de rotas filhas. Assim, a rota 'admin' possui várias rotas filhas.
Além disso essa é a forma de indicar para o Angular que o AdminComponent deve ser usado
como shell component, ou seja, o Template dele contém o elemento router-outlet .
Note também que há uma mudança na forma de indicar as rotas para os metadados do
módulo: o atributo imports contém uma chamada para RouterModule.forChild() , que
recebe como parâmetro o array routes . Esse é uma diferença marcante para a forma de
indicar as rotas para o root module, quando é utilizado o método RouterModule.forRoot() .
Outro passo necessário é importar o módulo de rotas no feature module. A seguir, um
trecho do AdminModule:
Feature-modules
107
...
@NgModule({
imports: [
CommonModule,
FormsModule,
HttpClientModule,
NgbModule,
AdminRoutingModule,
SharedModule
],
...
})
export class AdminModule {
}
O penúltimo elemento do array imports é o módulo de rotas ( AdminRoutingModule ).
O módulo de rotas para o módulo Publico , chamado PublicoRouting , contém um
conteúdo semelhante ao seguinte:
...
const routes: Routes = [
{
path: '', component: PublicoComponent, children: [
{path: '', component: LoginComponent}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PublicoRoutingModule {
}
O recurso de rotas filhas também é utilizado aqui. PublicoComponent é o shell component. O
fato interessante é que o atributo path contém o mesmo valor '' nas duas rotas. Como o
Angular considera '' a rota padrão, isso quer dizer que o PublicoModule tem maior
prioridade no momento em o Angular procurar encontrar uma rota com base na URL.
Incluindo as rotas dos feature modules no root
module
Feature-modules
108
O último passo desse processo é importar os feature modules no root module. A seguir,
um trecho do AppModule :
...
@NgModule({
imports: [
BrowserModule,
NgbModule.forRoot(),
FormsModule,
HttpClientModule,
AdminModule,
PublicoModule,
AppRoutingModule
],
...
})
export class AppModule {
}
Os módulos AdminModule , PublicoModule e AppRoutingModule são importados por meio do
atributo imports dos metadados da classe AppModule . Como os dois primeiros importam
seus respectivos módulos de rotas, o root module também tem conhecimento delas e,
assim, consegue aplicar o mesmo processo anterior de procura de rotas quando o browser
fornece uma URL.
Um aspecto importante é que a importação segue uma ordem. Veja que, primeiro, é
importado o módulo AdminModule , depois o PublicoModule e, por fim, o AppRoutingModule .
Como você viu no capítulo anterior o processo de procura de rotas percorre uma lista de
rotas do início até encontrar uma rota correspondente. Nesse caso a ordem das
importações dos módulos quer dizer para o Angular considerar, primeiro, as rotas do
AdminModule . Isso é muito importante porque as rotas do PublicoModule , por serem a rota
padrão, devem estar no final do processo de procura de rotas.
Shared modules
O Angular utiliza o conceito de shared modules para indicar módulos que fornecem
recursos que são usados por outros. Conforme a arquitetura do software até o momento o
serviço AuthService implementa a lógica de negócio de autenticação. Para ele poder ser
usado nos módulos AdminModule e PublicoModule esse elemento do software deve estar
em um módulo compartilhado. Se ele estivesse, por exemplo, no módulo PublicoModule e
o AdminModule o importasse isso geraria problemas de ordem ou ciclos de importação.
Feature-modules
109
Assim, a solução é isolar o AuthService em outro módulo, no caso o SharedModule. Na
prática, um shared module é também um feature module, mas, provavelmente, ele
apenas fornecerá recursos para serem utilizados em outros feature modules.
Posteriormente, cada feature module importa o shared module quando precisar utilizar
seus recursos.
Fluxo de trabalho
O fluxo de trabalho do capítulo anterior acaba de ser atualizado -- e também aumentou em
complexidade. Da mesma forma como antes, seguir os passos do processo também ajuda
a compreendê-lo:
1. criar feature module: essa agora é a primeira coisa a fazer;
2. criar componente: crie os componentes do feature module. Continue conduzido esse
processo de forma iterativa, não se preocupe em construir o componente por inteiro,
deixe-o como criado pelo Angular CLI;
3. definir rotas: depois, defina as rotas no módulo de routas;
4. implementar a lógica de negócio em um serviço: utilize serviços para implementar a
lógica de negócio; e
5. implementar lógica do componente e usar o serviço: volte a trabalhar com cada
compomente.
Feature-modules
110
Introdução
Iniciando com o Angular
Angular CLI
Software de gerenciamento escolar
Criando o projeto
Depurando código no browser
Iniciando com teste de software
Apresentando a lista de disciplinas
Cadastrando uma disciplina
Excluindo uma disciplina
Editando dados de uma disciplina
Interação entre componentes
Serviços
Acessando dados externos de forma assíncrona
Acessando dados de uma API HTTP
Rotas
Feature-modules