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 declarationspara 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 selectorindica 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;
- 
ElementRefquando é um elemento HTML qualquer;
- 
TemplateRefquando é 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 OnChangestrás ajuda de tipos para o métodongOnChanges.
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 providersdo componente ou módulo;
- Por exemplo, se você passar o serviço MeuServicoaoprovidersdo componenteMeuComponente, uma instância desse serviço (new MeuServico()) será criada para cadaMeuComponente. QuandoMeuComponentefor 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 - MeuServicofoi "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:
- 
ActivatedRoutepra 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 diretivarouterLinkfornecida 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)