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)