DEV Community

Cover image for Uma introdução rápida ao Angular
wkrueger
wkrueger

Posted on • Edited on

Uma introdução rápida ao Angular

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

Estrutura inicial

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)) 
Enter fullscreen mode Exit fullscreen mode

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 {} 
Enter fullscreen mode Exit fullscreen mode
  • 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"] } 
Enter fullscreen mode Exit fullscreen mode
  • 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> 
Enter fullscreen mode Exit fullscreen mode

Passando atributos

<!-- 1 --> <componente entrada="algumValor"></componente> <!-- 2 --> <componente [entrada]="umaLista"></componente> <!-- 3 --> <componente (saida)="algumaFuncao($event)"></componente> <!-- 4 --> <componente [(twoWay)]="variavel"></componente> 
Enter fullscreen mode Exit fullscreen mode
  1. Passa a string literal "algumValor" para o parâmetro;
  2. Passa a propriedade declarada na classe para o parâmetro (no caso, umaLista= ["um", "dois", "três"])
  3. Quando componentes emitem eventos, usam-se parênteses. Ex: (click), (hover), (submit);
  4. A sintaxe [(twoWay)] é um atalho para [twoWay]="variavel" (twoWayChange)="variavel = $event";

Ver property binding;

Loops

<div *ngFor="let item of lista">{{ item }}</div> 
Enter fullscreen mode Exit fullscreen mode

Condicionais

<div *ngIf="algumValor"></div> 
Enter fullscreen mode Exit fullscreen mode

Ver structural directives;

CSS condicional

<div [class.active]="isActive"></div> 
Enter fullscreen mode Exit fullscreen mode

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') } } 
Enter fullscreen mode Exit fullscreen mode
// 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() } } 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode
  • 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 } } 
Enter fullscreen mode Exit fullscreen mode
@Component({ selector: "app-consumer", template: `<app-some-component texto="Clique aqui"></some-component>`, }) export class ConsumerComponent {} 
Enter fullscreen mode Exit fullscreen mode
  • 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étodo ngOnChanges.

Saída

Um componente envia sinais de saída a partir de EventEmitters 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()) } } 
Enter fullscreen mode Exit fullscreen mode

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 } } 
Enter fullscreen mode Exit fullscreen mode

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 ao providers do componente MeuComponente, uma instância desse serviço (new MeuServico()) será criada para cada MeuComponente. Quando MeuComponente for destruído, a instância do serviço também é destruída e é invocado o método ngOnDestroy();
  • 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 {} 
Enter fullscreen mode Exit fullscreen mode

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() } } 
Enter fullscreen mode Exit fullscreen mode
  • Diz-se aqui que uma instância de MeuServico foi "injetada" em MeuComponente;

  • 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 {} 
Enter fullscreen mode Exit fullscreen mode
  • 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 {} 
Enter fullscreen mode Exit fullscreen mode
  • 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' } }) } } 
Enter fullscreen mode Exit fullscreen mode
  • 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 diretiva routerLink fornecida pelo roteador.
<a [routerLink]="['/hero', hero.id]">Goto hero</a> 
Enter fullscreen mode Exit fullscreen mode
  • 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):

(Doguinho aleatório da capa by Alexandru Rotariu from Pexels)

Muito obrigado!

Top comments (0)