OBS: Este texto está desatualizado, a versão mais refinada está publicada em: https://wkrueger.gitbook.io/angular/
Angular é a última framework de frontend que aprendi. Antes de trabalhar com ela, eu tinha um pouco de receio de aprendê-la pois a documentação inicial parecia um tanto assustadora. Por outro lado, após começar a trabalhar com ela, vi que não é tão complicada assim, a documentação é que não ajuda...
Neste texto, tento trazer um apanhado BEM resumido de como fazer algumas tarefas comuns no Angular, adicionando links para as fatias relevantes da documentação oficial.
O texto presume conhecimento prévio de desenvolvimento web (HTML/CSS e JS). Também não explico aqui conceitos do Typescript, mas tendo conhecimento em JS dá pra entender o texto.
Ferramentas Recomendadas
- Node.js
- VS Code
- Plugin "Angular Language Service" para o VS Code
- Plugin "angular inline 2"
Comparação
Onde o Angular pode parecer mais fácil, se comparado ao React
(alguns comentários daqui são voltados a quem já lidou com React)
- O uso de templates HTML e folhas de estilo (ao invés de JSX e CSS-in-JS) é algo mais familiar a pessoas com experiência prévia de web;
- (afirmo aqui que) O uso do padrão de injeção de dependências é mais simples e eficiente no gerenciamento de estado e na escrita de mocks para testes se comparado a alternativas populares do React (Redux);
- Não é necessário se preocupar em fazer alterações imutáveis de estado (na maioria das situações); O re-render é mais "automágico";
- A framework abstrai para si a complexa configuração de build e "code splitting", você geralmente não toca em configuração de webpack;
- Módulos sugeridos de formulários e roteador podem não ter a melhor experiência, mas são estáveis;
Iniciando
- Instale o Angular CLI
npm install -g @angular/cli
- Crie um esqueleto de projeto
ng new <projeto>
- Inicie a aplicação com
npm start
Estrutura
Terminologia
Sempre quando falo em template Angular, me refiro a quando se escreve sintaxe de layout (similar ao HTML).
Esta sintaxe é parecida mas não é exatamente HTML pois possui recursos adicionais. O navegador não reconheceria.
Módulos
O ponto de entrada de uma aplicação Angular é o arquivo main.ts (gerado pelo CLI). Esta aponta para o módulo raiz (no caso abaixo, AppModule
).
// main.ts // ... platformBrowserDynamic() .bootstrapModule(AppModule) // <-- AppModule é o módulo de entrada .catch((err) => console.error(err))
O módulo raiz aponta que o componente raiz será o AppComponent
, via propriedade bootstrap
:
// app/app.module.ts // ... import { AppComponent } from "./app.component" @NgModule({ declarations: [AppComponent], //Componentes e diretivas aqui imports: [BrowserModule, AppRoutingModule], // Outros módulos aqui providers: [], // Serviços aqui bootstrap: [AppComponent], // AppComponent é o módulo de entrada }) export class AppModule {}
- Componentes do Angular devem ser registrados na propriedade
declarations
para estarem disponíveis em templates. - A aplicação pode ser dividida em submódulos para podermos configurar o code splitting (não é o intuito desse texto). O code splitting é usado pra dividir sua aplicação em partes menores e evitar que o browser fique 1 minuto parado em uma tela branca pra carregar um JS de 15MB.
Docs: introduction to modules.
Componentes
No Angular, um componente é um bloco que une:
- Um template "Angular HTML";
- Uma folha de estilos com escopo isolado (CSS ou SCSS);
- Um arquivo TS para os metadados, estado e a lógica do componente.
O template "Angular HTML" aceita sintaxes específicas do Angular, dentre elas:
- Outros componentes podem ser incluídos a partir de suas tags ex:
<meu-componente></meu-componente>
; - Diretivas do Angular podem criar propriedades personalizadas de elementos. Ex:
<div tooltipText="Hello world"></div>
Um componente é iniciado a partir de uma classe anotada com @Component
. As propriedades selector e template(Url) são obrigatórias.
import { Component } from "@angular/core" @Component({ selector: "app-root", template /* ou templateUrl */: `<p>Olá mundo</p>`, styleUrls /* ou styles */: ["./app.component.scss"], }) export class AppComponent { algumValor = "Olá mundo" umaLista = ["um", "dois", "três"] }
- O atributo
selector
indica como este componente será chamado dentro do template. - No arquivo de módulo, este componente deve ser registrado na chave
declarations
; - O template e os estilos podem ser declarados ou "em linha" (
template
) ou em outro arquivo (templateUrl
).
Docs: introduction to components;
Sintaxe do template Angular
- Alguns dos recursos suportados pelo template Angular:
Interpolação de variáveis
- Qualquer propriedade da classe de componente é disponível no template.
- Para interpolação usam-se chaves duplas.
<p>Interpola: {{ algumValor }}</p>
Passando atributos
<!-- 1 --> <componente entrada="algumValor"></componente> <!-- 2 --> <componente [entrada]="umaLista"></componente> <!-- 3 --> <componente (saida)="algumaFuncao($event)"></componente> <!-- 4 --> <componente [(twoWay)]="variavel"></componente>
- Passa a string literal
"algumValor"
para o parâmetro; - Passa a propriedade declarada na classe para o parâmetro (no caso, umaLista= ["um", "dois", "três"])
- Quando componentes emitem eventos, usam-se parênteses. Ex:
(click)
,(hover)
,(submit)
; - A sintaxe
[(twoWay)]
é um atalho para[twoWay]="variavel" (twoWayChange)="variavel = $event"
;
Ver property binding;
Loops
<div *ngFor="let item of lista">{{ item }}</div>
Condicionais
<div *ngIf="algumValor"></div>
CSS condicional
<div [class.active]="isActive"></div>
Adiciona a classe active
se a variável for verdadeira.
Mais informações em attribute, class and style bindings;
Referências
- Elementos em um template podem ser referenciados na respectiva classe com a anotação
@ViewChild()
; - Anotações com
#
são usadas pra auxiliar referências.
// date-picker.component.ts @Component({ selector: 'date-picker', ... }) export class DatePickerComponent { pickDate() { console.log('date picked') } }
// app.component.ts @Component({ template: ` <date-picker #datePicker></date-picker> <div #theDiv>Hello</div> `, }) export class AppComponent { @ViewChild("datePicker") datePickerElement1!: DatePickerComponent // ou @ViewChild(DatePickerComponent) datePickerElement2!: DatePickerComponent @ViewChild("theDiv") divElement!: ElementRef ngAfterViewInit() { this.datePickerElement1.pickDate() } }
Observe que, para a anotação de tipo (que é opcional) usa-se:
- A própria classe do componente (
DatePickerComponent
), quando o elemento referenciado é um componente Angular; -
ElementRef
quando é um elemento HTML qualquer; -
TemplateRef
quando é uma tag<ng-template>
ng-container
Se deseja aplicar *ngIf ou *ngFor sem criar uma div para isso, use ng-container.
<ng-container *ngFor="let num of umaLista">{{ num }}</ng-container>
ng-template
Os elementos dentro de um ng-template
não são diretamente renderizados. Ele é usado para passar um bloco de HTML como variável a um componente ou função (exemplo: um modal). Você verá ele sendo usado em bibliotecas de interface de usuário (ex: Material, Bootstrap, etc).
Exemplo (abaixo): Uma biblioteca especifica a diretiva hoverPopup
que recebe como entrada uma seção de template. Ao passar o mouse por cima deste componente, um popup com este HTML é exibido.
<ng-template #popup> <p>Bem vindo</p> </ng-template> <label [hoverPopup]="popup">Exibir</label>
Mais informações na documentação do template Angular.
Notas sobre estilos
- Estilos de componentes possuem escopo restrito e isolado a esses componentes. Isto significa que componentes filho, por padrão, não recebem o estilo do componente pai;
- Este comportamento pode ser burlado com o seletor
::ng-deep
*;
(*) O seletor ng-deep é deprecado mas não tende a ser substituído até que um equivalente seja oficialmente publicado nas especificações do CSS.
.componente-filho ::ng-deep svg { stroke: black; } :host { /* estilos *deste* componente */ display: block; }
O seletor
:host
é usado pra aplicar estilos à raiz do componente;Além dos estilos isolados de componente, o projeto também conta com estilos globais, que são os presentes na raiz
src/styles.scss
.
Fluxo de dados
Duas das principais formas de trasmitir dados por uma aplicação Angular são:
- Propriedades de entrada e saída de componentes;
- Injeção de dependência (serviços);
Propriedades de entrada e saída
Entrada
Anote uma propriedade com @Input()
para amarrá-la a uma entrada do componente.
@Component({ selector: "app-some-component", template: `<button type="button">{{ texto }}</button>`, }) export class SomeComponent implements OnChanges { @Input() texto = "" ngOnChanges(changes) { // fazer algo } }
@Component({ selector: "app-consumer", template: `<app-some-component texto="Clique aqui"></some-component>`, }) export class ConsumerComponent {}
- No exemplo acima, um botão é desenhado com o conteúdo "Clique aqui".
- O método opcional
ngOnChanges
é chamado sempre que uma@Input()
sofrer alteração. - A interface (também opcional)
implements OnChanges
trás ajuda de tipos para o métodongOnChanges
.
Saída
Um componente envia sinais de saída a partir de EventEmitter
s anotados com @Output()
;
- Ao escrever
EventEmitter
, o editor dará várias sugestões. Selecione a pertencente ao@angular/core
.
Emissor:
@Component({ selector: "app-output", template: `<button type="button" (click)="processarClique($event)">Click me</button>`, }) class OutputComponent { @Output() fuiClicado = new EventEmitter<Date>() processarClique(ev) { this.fuiClicado.emit(new Date()) } }
Consumidor
@Component({ selector: "app-consumer", template: `<app-output (fuiClicado)="tratar($event)"></app-output>`, }) class ConsumerComponent { tratar(ev) { console.log(ev) // irá logar a Data atual } }
Ver inputs and outputs.
Serviços e injeção de dependência
Uma classe anotada com @Injectable()
pode ser atrelada a um módulo ou componente;
- Você define a quem o "injetável" está atrelado passando a classe ao atributo
providers
do componente ou módulo; - Por exemplo, se você passar o serviço
MeuServico
aoproviders
do componenteMeuComponente
, uma instância desse serviço (new MeuServico()
) será criada para cadaMeuComponente
. QuandoMeuComponente
for destruído, a instância do serviço também é destruída e é invocado o métodongOnDestroy()
; - Se você passar um serviço ao módulo raiz, este serviço efetivamente será um Singleton (instância global).
@Injectable() class MeuServico { dizerAlgo() { console.log('algo') } } @Module({ ..., providers: [MeuServico] }) class MeuModulo {} @Component({ ..., providers /* ou viewProviders */: [MeuServico] }) class MeuComponente {}
Consumindo o serviço
Acesse o serviço o passando como parâmetro no contrutor da classe.
@Component(...) class MeuComponente { constructor(private meuServico: MeuServico) {} aoClicar() { this.meuServico.dizerAlgo() } }
Diz-se aqui que uma instância de
MeuServico
foi "injetada" emMeuComponente
;Caso o serviço não tenha sido especificado em nenhuma chave
providers
, o Angular vai reclamar;Caso o serviço tenha sido providenciado em vários lugares (no módulo e no componente), a instância mais local (a do componente) é fornecida;
providers vs. viewProviders
Serviços fornecidos pela chave providers
de um módulo são acessíveis em todos os componentes deste módulo.
Por outro lado, quando um serviço é passado na chave providers
de um componente, ele não é acessível para injeção nos componentes filho.
Quando o serviço é fornecido em um componente pela chave viewProviders
, este é também acessível nos componentes filhos.
Serviços fornecidos a módulos e a viewProviders trazem funcionalidade paralela ao que faz o "context" no React.
Qual é a utilidade desta complicação
Além da funcionalidade de delimitação de contexto, a injeção de dependências é de grande utilidade em mocks para testes.
Quando uma classe especifica que quer consumir ServicoA
, ela não necessariamente recebe a classe exata ServicoA
. Você pode passar qualquer outra classe ao providers
que atenda ao mesmo contrato. Ex: As ferramentas de teste permitem que se instanciem módulos injetando serviços "dublês".
Documentação: introduction to services and dependency injection;
Roteador
O Angular gera uma aplicação "single page", e o roteador é um componente importantíssimo neste contexto. O roteador permite com que a aplicação não seja totalmente recarregada quando se troca de página.
O que o roteador faz? Em resumo:
- Providencia um componente
<router-outlet>
;
Exemplo: No boilerplate padrão do Angular, o <router-outlet>
é elemento único do componente raiz.
@Component({ selector: "app-root", template: ` <router-outlet></router-outlet> `, styles: [], }) export class AppComponent {}
- Solicita a configuração de um mapeamento entre URLs e:
- Componentes ou
- Módulos com subroteadores ou
- Redirecionamentos
Exemplo
const routing = RouterModule.forRoot([ { path: "", component: IntroComponent }, { path: "gato/:id", component: GatoComponent }, { path: "cachorro", loadChildren: () => import("./Cachorro/Cachorro.module").then((m) => m.CachorroModule), // usado para "code splitting" }, { path: "capivara", children: [...] }, { path: "**", redirectTo: '' } ]) @Module({ ... imports: [routing, ...] ... }) export class AppModule {}
- Sempre que um URL é alterado (ou no carregamento inicial de uma página), o componente correspondente é carregado no "outlet";
- Fornece os serviços a seguir que podem ser injetados:
-
ActivatedRoute
pra determinarmos informações sobre o estado do roteador. Como: qual rota está ativada? Quais os parâmetros de URL? -
Router
, que é usado pra controlar o roteador (ir para...);
-
@Component({ ... }) class AlgumComponente { constructor(private route: ActivatedRoute, private router: Router) {} async ngOnInit() { const queryParams = await this.route.queryParams.pipe(take(1)).toPromise() console.log(queryParams) } goto() { this.router.navigate('/someroute', { queryParams: { hello: 'world' } }) } }
- O uso de links padrão do HTML (
<a href="/page"/>
) não é amigável pra SPA's pois fazem recarregar a página toda; Deve ser usada ao invés disso a diretivarouterLink
fornecida pelo roteador.
<a [routerLink]="['/hero', hero.id]">Goto hero</a>
- Subroteadores e múltiplos outlets: Conforme apontado anteriormente, é possível haver "roteadores filho". Neste caso haverá no HTML um
<router-outlet>
dentro de outro. Há também funcionalidade avançada onde um roteador pode controlar múltiplos outlets.
Mais informações no (extenso) guia do roteador.
O que mais falta
A idéia deste artigo é dar uma introdução rápida ao mais importante da framework, já que a documentação oficial pode ser um tanto intimidadora ao iniciante. Por este ser um breve resumo, muita coisa foi deixada de fora pra não tornar o artigo muito extenso. Alguns dos principais pontos adicionais para consultar futuramente (e seus respectivos links pra doc oficial):
- Módulo de formulários;
- Publicando a aplicação;
- Ciclo de vida do componente;
- Testes e2e e unitários
- Usando (ou fugindo do) RxJS;
- Usando (ou fugindo do) cliente HTTP do Angular;
- Dentre outros...
(Doguinho aleatório da capa by Alexandru Rotariu from Pexels)
Muito obrigado!
Top comments (0)