DEV Community

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

Uma introdução rápida ao Angular

wkrueger profile image Willian Krueger Updated on ・9 min read

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))

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>
  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>

Condicionais

<div *ngIf="algumValor"></div>

Ver structural directives;

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é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())
  }
}

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 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 {}

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" 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 {}
  • 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 diretiva routerLink 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):

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

Muito obrigado!

Discussion (0)

pic
Editor guide