Artigo original: The Best Angular Examples

Tradução realizada em português europeu

Angular é um framework de código-livre com base no TypeScript, utilizado para desenvolver aplicações de front-end para a web. É o sucessor do AngularJS e todas as menções ao Angular referem-se à versão 2 ou superior. O Angular tem funcionalidades como generics, static-typing e algumas outras funcionalidades do ES6.

Histórico de versões

A Google lançou a versão inicial do AngularJS a 20 de outubro de 2010. A versão estável do AngularJS foi lançada a 18 de dezembro de 2017, com a versão 1.6.8. A última atualização significativa do AngularJS, a versão 1.7, ocorreu a 1 de julho de 2018, e está, atualmente, num período de 3 anos de Suporte a Longo Prazo. O Angular 2.0 foi anunciado pela primeira vez a 22 de setembro de 2014, na conferência ng-Europe. Uma das novas funcionalidades do Angular 2.0 é o carregamento dinâmico. A maior parte das principais funcionalidades foram movidas para módulos.

Após algumas modificações, o Angular 4.0 foi lançado em dezembro de 2016. O Angular 4 tem compatibilidade com a versão mais antiga do Angular 2.0. Algumas das novas funcionalidades são a biblioteca HttpClient e os novos eventos de ciclo de vida do router. O Angular 5 foi lançado a 1 de novembro de 2017, tendo uma funcionalidade muito importante que é o suporte para aplicações web progressivas. O Angular 6 foi lançado em maio de 2018, enquanto o Angular 7 foi lançado em outubro de 2018. A versão estável mais recente é a 7.0.0.

Nota da tradução: o texto que estás a ler é de 2018. A versão mais recente do Angular, no momento desta tradução, é a versão 16.1.2, de 21 de junho de 2023, compatível com as versões 16.14.0 a 18.10.0 do NodeJS, 4.9.3 a 5.2.0 do TypeScript e 6.5.3 a 7.4.0 do RxJS.

Instalação

A forma mais fácil de instalar o Angular é através da Angular CLI. Esta ferramenta permite a criação de projetos e a geração de componentes, serviços, módulos e assim adiante. Como padrão, a equipa do Angular considera que esta seja a melhor prática.

Angular 2.x e versões mais recentes

Instalar a Angular CLI

npm install -g @angular/cli

Criar um espaço de trabalho e a aplicação inicial

Vais desenvolver aplicações no contexto de um espaço de trabalho do Angular. Um espaço de trabalho contém os ficheiros para um ou mais projetos. Um projeto é o conjunto de ficheiros que compõe uma aplicação, uma biblioteca, ou testes end-to-end (e2e).

ng new my-app

Servir a aplicação

O Angular inclui um servidor para que consigas criar e servir facilmente a tua aplicação localmente.

  1. Navega até à pasta do espaço de trabalho (my-app)

Inicia o servidor ao utilizar o comando CLI ng serve com a opção --open

cd my-app
ng serve --open

Boa, criaste a tua primeira aplicação em Angular!!!

O Angular contém muitos esquemas para criar aplicações. Os componentes são um desses esquemas. Eles englobam uma única unidade de lógica relacionada com uma única parte da aplicação. Os componentes fazem geralmente parceria com outros esquemas para operar de maneira mais eficiente.

Os componentes simplificam a aplicação. Canalizar a lógica para uma única secção da interface visível é o seu principal objetivo. Para criar aplicações passo a passo, precisas de criar componente a componente. No final de contas, os componentes funcionam como blocos de construção do Angular.

Classe de componente e metadados

O comando da CLI ng generate component [nome-do-componente] produz o seguinte:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {
  constructor() { }

  ngOnInit() { }
}

Esta é a estrutura básica da qual todos os grandes componentes se originam. O decorator @Component é a parte mais importante. Sem ele, o exemplo acima torna-se uma classe genérica. O Angular depende dos decorators para distinguir o tipo esquemático da classe.

@Component recebe metadados como um único objeto. Os decorators são apenas funções JavaScript em segundo plano. Eles recebem argumentos assim como o objeto de metadados. O objeto de metadados configura as dependências básicas do componente. Cada campo tem o seu papel.

  • selector: indica ao Angular para associar o componente a um determinado elemento no template HTML da aplicação.
  • templateUrl: aceita a localização do ficheiro do template HTML do componente (é também aqui que a informação é exibida).
  • styleUrls: aceita um array de localizações de ficheiros das folhas de estilos (strings). Estas folhas de estilos focam o template atribuído ao componente.

Pensa em metadados como uma grande bolha de configuração. O decorator recebe-os de maneira a que possa gerar a informação específica ao componente. O decorator decora a classe subjacente com informação necessária para o comportamento da sua classe, ou seja, uma classe de componente.

A assinatura da classe é exportada por padrão, de maneira a que o componente possa ser importado. ngOnInit é também implementado. implements indica à classe para definir certos métodos de acordo com a definição da interface. ngOnInit é um hook de ciclo de vida.

Informações de componente

A informação comanda tudo. Os componentes não são exceção. Os componentes englobam toda a informação. Para receber dados externamente, um componente deve declarar isso explicitamente. Este modo de privacidade impede que a informação entre em conflito ao longo da árvore de componentes.

A informação determina o que é exibido da classe de componente para o teu template. Qualquer atualização à informação da classe atualizará (ou, pelo menos, deverá atualizar) a visualização do template.

Os componentes, geralmente, vão inicializar um conjunto de membros (ou variáveis) que armazenam informação. São utilizados ao longo da lógica da classe de componente por conveniência. Essa informação alimenta a lógica resultante no template e o seu comportamento. Observa o seguinte exemplo:

// ./components/example/example.component.ts

import { Component, OnInit } from '@angular/core';
import { Post, DATA } from '../../data/posts.data';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit {
  username: string;
  totalPosts: number;
  allPosts: Post[];

  deletePost(index: number): void {
    this.allPosts.splice(index, 1);
    this.totalPosts = this.allPosts.length;
  }

  ngOnInit(): void {
    this.username = DATA.author;
    this.totalPosts = DATA.thePosts.length;
    this.allPosts = DATA.thePosts;
  }
}
<!-- ./components/example/example.component.html -->

<h1>{{ username }}</h1>
<span>Alterar o nome: </span><input [(ngModel)]="username">
<h3>Publicações: {{ totalPosts }}</h3>
<ul>
<hr/>
<div *ngFor="let post of allPosts; let i=index">
  <button (click)="deletePost(i)">EXCLUIR</button>
  <h6>{{ post.title }}</h6>
  <p>{{ post.body }}</p>
  <hr/>
</div>
</ul>

Repara nas formas como o componente interage com a informação. Primeiro, vai buscar a ../../data/posts.data antes de começar a encaminhar para o template para exibir.

A informação aparece ao longo do template. Dentro de chavetas, o valor de uma variável é mapeado da classe de componente para as chavetas. O *ngFor itera pela classe de array allPosts. Clicar no botão remove um elemento específico de allPosts pelo seu índice. Podes até alterar o username mais acima ao escrever na caixa de input.

As interações acima alteram a informação da classe de componente que, por sua vez, altera o template HTML do componente. Os componentes fornecem a lógica principal, que facilita o fluxo da informação. O template HTML torna essa informação legível para o utilizador.

Ligação (binding) dos dados

A informação define geralmente o aspeto de uma aplicação. Interpretar essa informação para a interface de utilizador envolve a lógica de classe (*.component.ts) e uma vista de template (*.component.html) . O Angular conecta-os através da ligação (do inglês, binding) dos dados. Pensa na ligação dos dados como uma ferramenta para interação de componentes.

Componente e template

O componente armazena maior parte da sua lógica e informação dentro da sua classe decorada com @Component. Esse decorator define a classe como um componente com template HTML. O template do componente representa a classe dentro da aplicação. Aqui, o foco deve ser entre a classe do componente e o template HTML.

É aqui que a ligação de dados ocorre. As propriedades de elementos e eventos recebem valores. Esses valores, definidos pela classe de componente, servem um de dois propósitos. Um é produzir informação que o template receberá. O outro é lidar com os eventos emitidos pelo elemento de template.

Propriedades de elemento

Para reconhecer propriedades de elemento vinculadas aos dados, o Angular utiliza uma sintaxe especial de chavetas.

// my.component.ts
@Component({
  templateUrl: './my.component.html'
})

export class MyComponent {
  value:type = /* algum valor de tipo */;
}
<!-- my.component.html -->
<any-element [property]="value">innerHTML</any-element>

Tem paciência comigo aqui.

[property] espelha a propriedade no nó do objeto do elemento no Domain Object Model (DOM). Não confundas propriedades de objeto com um atributo de elemento do DOM. As propriedades e os atributos partilham por vezes o mesmo nome e fazem a mesma coisa. No entanto, existe uma clara distinção.

Lembra-te de que attr (atributos) é uma única propriedade do objeto do DOM subjacente. A declaração é feita na instanciação do DOM, com valores de atributo correspondentes à definição do elemento. O elemento mantém o mesmo valor após isso. Cada propriedade tem o seu próprio campo chave-valor num nó de objeto do DOM. Essas propriedades são mutáveis após a instanciação.

Compreende a diferença entre atributos e propriedades. Isso levará a uma melhor compreensão de como o Angular vincula a informação às propriedades (vinculação de propriedade). O Angular raramente vinculará a informação ao atributo de um elemento. As excepções a isso são muito raras. Uma última vez: o Angular vincula a informação de componente às propriedades, não aos atributos!

Voltando ao exemplo, o [ … ] na atribuição da propriedade do elemento tem um significado especial. Os parênteses retos mostram que property está vinculado ao "value" à direita da atribuição.

value também tem significado especial no contexto dos parênteses retos. O próprio value é uma string literal. O Angular faz a leitura e compara o seu valor ao dos membros da classe. O Angular substituirá o valor do atributo de membro correspondente. Claro que isto refere-se à mesma classe de componente que hospeda o template HTML.

O fluxo unidirecional de informação do componente para o template está completo. O membro correspondente à atribuição correta da propriedade entre parênteses retos fornece o value. Nota que alterações ao valor do membro na classe do componente filtram para baixo para o template. Essa é a deteção de alteração do Angular em funcionamento. Alterações dentro do âmbito do template não tem efeito no membro da classe do componente.

Informação principal: a classe de componente fornece a informação enquanto que o template a exibe.

Não mencionei que os valores da informação também podem aparecer num innerHTML de um componente. Este último exemplo implementa chavetas duplas. O Angular reconhece essas chavetas e interpola a classe de dados de componente correspondente para o innerHTML da div.

<div>O valor do membro 'value', da classe de componente, é {{value}}.</div>

Lidar com eventos

Se o componente fornece dados, então o template fornece eventos.

// my.component.ts
@Component({
  templateUrl: './my.component.html'
})

export class MyComponent {
  handler(event):void {
      // a função faz algo
  }
}
// my.component.html
<any-element (event)=“handler($event)”>innerHTML</any-element>

Isto funciona de maneira semelhante à vinculação de propriedades.

O (event) refere-se a qualquer tipo de evento válido. Por exemplo, um dos tipos de eventos mais comum é o click. Ele emite quando clicas com o rato. Independentemente do tipo, o event está vinculado ao "handler" no exemplo. Os gestores de eventos são geralmente funções de membro da classe de componente.

Os ( … ) são especiais para o Angular. Os parênteses indicam ao Angular que um evento está vinculado à atribuição correta do handler. O próprio evento origina a partir do elemento hospedeiro.

Quando o evento emite, ele passa o objeto Event sob a forma de $event. O handler mapeia para a função que tem o mesmo nome do handler da classe de componente. A troca unidirecional do elemento vinculado ao evento para a classe de componente está finalizada.

Os eventos de emissão do gestor, enquanto possível, não causam impacto no elemento de template. Afinal, a vinculação é unidirecional.

Diretrizes

Diretrizes são elementos de componente e atributos criados e reconhecidos pelo Angular. O Angular associa o elemento ou atributo com a sua definição de classe correspondente. @Directive ou @Component decora essas classes. Ambos são indicativos para o Angular que a classe funciona como uma diretriz.

Algumas diretrizes modificam o estilo do elemento hospedeiro. Outras diretrizes exibem vistas ou inserem em vistas já existentes como vistas incorporadas. Por outras palavras, elas alteram o layout HTML.

De qualquer forma, as diretrizes sinalizam o compilador do Angular. Elas marcam componentes para modificação, dependendo da lógica da classe da diretriz.

Diretriz estrutural

Aqui estão três exemplos de diretrizes estruturais. Cada uma tem uma contrapartida lógica (if, for e switch).

  • *ngIf
  • *ngFor
  • *ngSwitchCase e *ngSwitchDefault

Nota importante: todas as três estão disponíveis através da importação do CommonModule, que está disponível a partir de @angular/common para importação dentro do módulo raiz da aplicação.

*ngIf

*ngIf testa um determinado valor para ver se é verdadeiro ou falso com base na avaliação booleana geral em JavaScript. Em caso de ser verdadeiro, o elemento e o seu innerHTML vão ser exibidos. Caso contrário, nunca vão renderizar para o Domain Object Model (DOM).

<!-- renders "<h1>Olá!</h1>" -->
<div *ngIf="true">
  <h1>Olá!</h1>
</div>

<!-- não renderiza -->
<div *ngIf="false">
  <h1>Alô!</h1>
</div>

Esse é um exemplo fictício. Qualquer valor de membro da classe de componente do template pode ser substituído por true ou false.

Nota: também podes fazer o seguinte com *ngIf para obter acesso ao valor observável:

<div *ngIf="observable$ | async as oNomeQueQuiseres">
  {{ oNomeQueQuiseres }}
</div>
*ngFor

*ngFor itera com base numa expressão, microssintica, atribuída à direita. A microssintaxe vai para além do âmbito deste artigo. Fica apenas a saber que a microssintaxe é uma forma abreviada de uma expressão lógica. Ocorre como uma única string capaz de referenciar valores de membro de classe. Podes iterar através de valores iteráveis, o que os torna úteis para *ngFor.

<ul>
  <li *ngFor=“let potato of ['Russet', 'Doce', 'Rosalinda']; let i=index”>
      Potato {{ i + 1 }}: {{ potato }}
  </li>
  <!-- Resultados
  <li>
      Batata 1: Russet
  </li>
  <li>
      Batata 2: Doce
  </li>
  <li>
      Batata 3: Rosalinda
  </li>
  -->
</ul>

['Russet', 'Doce', 'Rosalinda'] é um valor iterável. Os arrays são uns dos valores iteráveis mais comuns. O *ngFor cria um <li></li> por cada elemento do array. É atribuído a cada elemento do array a variável potato (em português, batata). Isto é feito ao utilizar a microssintaxe. O *ngFor define o conteúdo estrutural do elemento ul. Isso é característico de uma diretriz estrutural.

Nota: também podes fazer o seguinte com a diretriz *ngFor para obter acesso ao valor observável (atalho):

<div *ngFor="let oNomeQueQuiseres of [(observable$ | async)]">
  {{  oNomeQueQuiseres }}
</div>
*ngSwitchCase e *ngSwitchDefault

Estas duas diretrizes estruturais trabalham em conjunto para fornecer a funcionalidade switch ao template HTML.

<div [ngSwitch]="potato">
  <h1 *ngSwitchCase="'Russet'">{{ potato }} é uma batata Russet.</h1>
  <h1 *ngSwitchCase="'Sweet'">{{ potato }} é uma batata doce.</h1>
  <h1 *ngSwitchCase="'Laura'">{{ potato }} é uma batata Rosalinda.</h1>
  <h1 *ngSwitchDefault>{{ potato }} é um tipo diferente de batata.</h1>
</div>

Apenas uma das expressões *ngSwitch… é renderizada. Repara no atributo [ngSwitch] dentro do elemento div a envolver o switch. Ele passa o valor de potato ao longo da cadeia *ngSwitch.... Essa cadeia de diretrizes estruturais determina que elemento h1 renderiza.

Como tal, [ngSwitch] não é uma diretriz estrutural ao contrário das instruções *ngSwitch…. Ele passa o valor enquanto que o bloco determina o layout final do HTML.

Lembra-te de que a estilização e passagem de valores não são da responsabilidade das diretrizes estruturais. Isto é responsabilidade das diretrizes de atributos. As diretrizes estruturais determinam apenas o layout.

As transformações à informação resultante garantem que a informação esteja num formato adequado quando chega a hora de carregar para o ecrã do utilizador. Normalmente, as transformações de informação ocorrem em segundo plano. Com pipes, a transformação de informação pode ocorrer no template HTML. Os pipes transformam a informação de template diretamente.

Os pipes têm bom aspeto e são convenientes. Eles ajudam a manter a classe de componente com poucas transformações básicas. Tecnicamente, os pipes expressam a lógica da transformação de informação.

Casos de utilização

O Angular vem carregado com um conjunto básico de pipes. Trabalhar com um par deles desenvolverá a intuição para lidar com os restantes. A lista a seguir fornece três exemplos:

  • AsyncPipe
  • DatePipe
  • TitleCasePipe
AsyncPipe

Essas secções requerem uma compreensão básica de Promises ou Observables para apreciar totalmente. O AsyncPipe opera num dos dois. O AsyncPipe extrai informação das Promises/Observables como resultado para o que vier a seguir.

No caso dos Observables, o AsyncPipe subscreve automaticamente para a fonte de informação. Independentemente de onde vier a informação, o AsyncPipe subscreve para a fonte observável. O async é o nome sintático do AsyncPipe, tal como apresentado abaixo.

<ul *ngFor=“let potato of (potatoSack$ | async); let i=index”>
  <li>Batata {{i + 1}}: {{potato}}</li>
</ul>

No exemplo, o potatoSack$ é um Observable a aguardar um carregamento de batatas. Assim que as batatas chegarem, quer de maneira síncrona, quer assíncrona, o AsyncPipe recebe-as como um array iterável. O elemento de lista é, então, preenchido por batatas.

DatePipe

Formatar strings de data requer um pouco de manipulação com o objeto de JavaScript Date. O DatePipe fornece uma forma potente de formatar datas assumindo que o input fornecido está num formato válido de data.

TitleCasePipe

Transforma texto para estilo de título. Coloca em maiúscula a primeira letra de cada palavra e transforma as letras restantes da palavra em minúsculas. As palavras são delimitadas por qualquer caracter de espaço em branco, tal como um espaço, um tab ou caracter de avanço de linha.

// example.component.ts

@Component({
  templateUrl: './example.component.html'
})
export class ExampleComponent {
  timestamp:string = ‘2018-05-24T19:38:11.103Z’;
}
<!-- example.component.html -->

<div>Hora atual: {{timestamp | date:‘short’}}</div>

O formato do timestamp acima é ISO 86011 — não é o mais fácil de ler. O DatePipe (date) transforma o formato de data ISO no formato mais convencional mm/dd/yy, hh:mm AM|PM. Existem muitas outras opções de formatação. Todas estas opções estão na documentação oficial.

Criar pipes

Embora o Angular tenha apenas um determinado número de pipes, o decorator @Pipe permite aos programadores criarem os seus próprios. O processo começa com ng generate pipe [nome-do-pipe], substituindo [nome-do-pipe] por um nome de ficheiro preferível. Este comando produz o seguinte:

import { Pipe, PipeTransform } from ‘@angular/core’;

@Pipe({
  name: 'example'
})
export class ExamplePipe implements PipeTransform {
  transform(value: any, args?: any): any {
      return null;
  }
}

Este template de pipe simplifica a criação de um pipe personalizado. O decorator @Pipe indica ao Angular que a classe é um pipe. O valor de name: ‘example’, sendo neste caso example, é o valor que o Angular reconhece quando procura no template HTML por pipes personalizados.

Avancemos para a lógica da classe. A implementação PipeTransform fornece as instruções para a função transform. Esta função tem significado especial dentro do contexto do decorator @Pipe. Ela recebe dois parâmetros de início.

value: any é o resultado que o pipe recebe. Pensa no <div>{{ umValor | example }}</div>. O valor de umValor é passado para o parâmetro value: any da função transform. Esta é a mesma função transform definida na classe ExamplePipe.

args?: any é qualquer argumento que o pipe recebe opcionalmente. Pensa em <div>{{ umValor | example:[um-argumento] }}</div>. [um-argumento] pode ser substituído por qualquer valor. Esse valor é passado para o parâmetro args?: any da função transform. Isto é, a função transform definida na classe do ExamplePipe.

O que quer que a função retorne, (return null;) passa a ser o resultado da operação de pipe. Observa o próximo exemplo para ver um exemplo completo de ExamplePipe. Dependendo da variável que o pipe recebe, ele passa o valor de entrada ou para maiúscula ou para minúscula  no novo resultado. Um argumento inválido ou inexistente fará com que o pipe retorne o mesmo valor de entrada como resultado.

// example.pipe.ts

@Pipe({
  name: 'example'
})
export class ExamplePipe implements PipeTransform {
  transform(value:string, args?:string): any {
    switch(args || null) {
      case 'uppercase':
        return value.toUpperCase();
      case 'lowercase':
        return value.toLowerCase();
      default:
        return value;
    }
  }
}
// app.component.ts

@Component({
  templateUrl: 'app.component.html'
})
export class AppComponent {
  someValue:string = "HeLlO WoRlD!";
}
<!-- app.component.html -->

<!-- Outputs “HeLlO WoRlD!” -->
<h6>{{ someValue | example }}</h6>

<!-- Outputs “HELLO WORLD!” -->
<h6>{{ someValue | example:‘uppercase’ }}</h6>

<!-- Outputs “hello world!” -->
<h6>{{ someValue | example:‘lowercase’ }}</h6>

Hooks do ciclo de vida

Os hooks do ciclo de vida são métodos temporizados. Eles diferem no tempo e na razão pela qual são executados. A alteração de deteção aciona estes métodos. Eles executam dependendo das condições do ciclo atual. O Angular executa constantemente deteção de alterações nos seus dados. Os hooks do ciclo de vida ajudam a gerir os seus efeitos.

Um aspeto importante destes hooks é a sua ordem de execução. Ela nunca se altera. Eles executam com base numa série previsível de eventos de carregamento produzidos a partir de um ciclo de deteção. Isto torna-os previsíveis. Alguns ativos estão apenas disponíveis depois da execução de um determinado hook. Claro, um hook executa apenas sob determinadas condições definidas na alteração atual do ciclo de deteção.

Por ordem de execução:

ngOnChanges

ngOnChanges é acionado seguindo a modificação do @Input vinculado aos membros da classe. Dados vinculados pelo decorator @Input() vêm de uma fonte externa. Quando uma fonte externa altera os dados numa forma detetável, eles são passados novamente pela propriedade @Input.

Com essa atualização, ngOnChanges aciona imediatamente. Também é acionado na inicialização dos dados de entrada. O hook recebe um parâmetro opcional do tipo SimpleChanges. Este valor contém informação das propriedades alteradas vinculadas ao valor de entrada.

ngOnInit

ngOnInit é acionado uma vez na inicialização da propriedade (@Input) de um componente vinculado aos dados. O próximo exemplo terá um aspeto semelhante ao anterior. O hook não aciona porque o ChildComponent recebe os dados de entrada. Em vez disso, ele aciona logo após a renderização da informação para o template do ChildComponent.

ngDoCheck

ngDoCheck é acionado a cada ciclo de deteção de alteração. O Angular executa frequentemente a deteção de alterações. Realizar qualquer ação fará com que faça mais um ciclo. O ngDoCheck é acionado com esses ciclos. Utiliza-o com precaução. Ele pode causar problemas de performance quando implementado incorretamente.

O ngDoCheck permite aos programadores verificarem os seus dados manualmente. Eles podem acionar uma nova data de aplicação condicionalmente. Juntamente com ChangeDetectorRef, os programadores podem criar as suas próprias verificações para a deteção de mudanças.

ngAfterContentInit

ngAfterContentInit é acionado depois da inicialização do DOM do conteúdo do componente (quando ele é carregado pela primeira vez). Esperar por queries @ContentChild(ren) é o principal caso de utilização do hook.

As queries @ContentChild(ren) produzem referências de elemento para o conteúdo do DOM. Como tal, elas não estão disponíveis até que o conteúdo do DOM carregue. É por isso que ngAfterContentInit e sua contrapartida, ngAfterContentChecked, são utilizados.

ngAfterContentChecked

ngAfterContentChecked é acionado depois de cada ciclo de deteção de alteração focado no DOM do conteúdo. Isto permite aos programadores facilitar como o DOM do conteúdo reage a deteção de alterações. O ngAfterContentChecked pode ser acionado frequentemente e causar problemas de performance se for mal implementado.

ngAfterContentChecked também é acionado durante as etapas de inicialização de um componente. Vem logo após o ngAfterContentInit.

ngAfterViewInit

ngAfterViewInit é acionado uma vez depois da vista do DOM terminar a inicialização. A vista carrega sempre logo após o conteúdo. O ngAfterViewInit espera pela resolução das queries @ViewChild(ren). Estes elementos são consultados a partir de dentro da mesma vista do componente.

ngAfterViewChecked

ngAfterViewChecked é acionado depois de qualquer ciclo de deteção de alterações focado na vista do componente. O hook ngAfterViewChecked permite aos programadores facilitar como a deteção de alterações afeta a vista do DOM.

ngOnDestroy

ngOnDestroy é acionado quando um componente é removido da vista e do DOM subsequente. Este hook fornece uma oportunidade para limpar qualquer ponta solta antes de remover um componente.

Vistas

As vistas são quase como o seu próprio DOM virtual. Cada vista contém uma referência para uma secção correspondente do DOM. Dentro de uma vista estão nós que espelham o que está nesta secção. O Angular atribui um nó de vista por cada elemento DOM. Cada nó tem uma referência para um elemento correspondente.

Quando o Angular procura por alterações, este verifica as vistas. O Angular evita o DOM em segundo plano. As vistas referenciam o DOM em seu nome. Existem outros mecanismos para garantir que as alterações de vista renderizem no DOM. Por outro lado, alterações ao DOM não afetam as vistas.

Novamente, as vistas são comuns ao longo de todas as plataformas de desenvolvimento para além do DOM. Mesmo no desenvolvimento para uma plataforma, as vistas ainda são consideradas melhor prática. Elas garantem que o Angular tenha uma interpretação correta do DOM.

As vistas podem não existir em bibliotecas externas. A manipulação direta do DOM é uma saída de emergência para este tipo de cenário. Certamente, não esperes que a aplicação funcione em várias plataformas.

Tipos de vistas

Existem dois tipos principais de vistas: incorporada e de anfitrião.

Também existem recipientes de vista. Eles armazenam vistas incorporadas e de anfitrião e são geralmente referidos como "vistas" simples.

Qualquer classe @Component regista um recipiente de vista (vista) com Angular. Novos componentes geram um seletor personalizado focado num determinado elemento do DOM. A vista anexa-se a esse elemento quando ele aparecer. O Angular sabe agora que o componente existe ao olhar para o modelo de vista.

Vistas de anfitrião e recipientes

Vistas de anfitrião hospedam componentes dinâmicos. Recipientes de vista (vistas) são anexados automaticamente a elementos já existentes no template. As vistas podem ser anexadas a qualquer elemento para além do que é único em classes de componente.

Pensa no método tradicional da geração de componente. Ele começa por criar uma classe, decorá-la com @Component, e preenchê-la com metadados. Esta abordagem ocorre em qualquer elemento de componente predefinido do template.

Tenta utilizar o comando da interface da linha de comandos do Angular (CLI): ng generate component [name-of-component]. Ele produz o seguinte:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent implements OnInit {
  constructor() { }

  ngOnInit() { }
}

Isso cria o componente com o seletor app-example. Ele anexa um recipiente de vista a <app-example></app-example> no template. Se essa for a raiz da aplicação, a sua vista encapsularia todas as outras vistas. A vista raiz marca o início da aplicação na perspetiva do Angular.

Criar componentes dinamicamente e registá-los no modelo de vista do Angular requer mais alguns passos. As diretrizes estruturais ajudam a gerir conteúdo dinâmico (*ngIf, *ngFor, e *ngSwitch…). No entanto, as diretrizes não escalam para aplicações maiores. Demasiadas diretrizes estruturais complicam o template.

É aqui que instanciar componentes a partir de lógicas de classe existentes vem a calhar. Estes componentes precisam de criar uma vista de anfitrião que pode inserir no modelo de vista. As vistas de anfitrião armazenam dados para componentes de maneira a que o Angular reconheça o seu propósito estrutural.

Vistas incorporadas

Diretrizes estruturais criam um ng-template à volta de um pedaço de conteúdo do HTML. O elemento anfitrião da diretriz tem um recipiente de vista anexado. Isto faz com que o conteúdo possa renderizar à condição no seu layout pretendido.

O ng-template armazena nós de vista incorporados a representar cada elemento dentro do seu innerHTML. ng-template não é de todo um elemento do DOM. Ele comenta-se a si próprio. As tags definem a extensão da sua vista incorporada.

Instanciar uma vista incorporada não requer recursos externos para além da sua própria referência. A query @ViewChild pode ir buscar isso.

Com a referência do template, chamar createEmbeddedView a partir dele dá conta do recado. O innerHTML da referência passa a ser a sua própria instância de vista incorporada.

No próximo exemplo, <ng-container></ng-container> é um recipiente de vista. O ng-container é comentado durante a compilação, tal como o ng-template. Por isso, ele fornece uma saída para inserir a vista incorporada mantendo o DOM leve.

O template de vista incorporada é inserido na localização de layout do ng-container. Esta vista recentemente inserida não tem encapsulamento de vista adicional para além do recipiente de vista. Lembra-te de como isto é diferente das vistas de anfitrião (as vistas de anfitrião são anexadas ao seu invólucro de elemento ng-component).

import { Component, AfterViewInit, ViewChild,
ViewContainerRef, TemplateRef } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `
  <h1>Application Content</h1>
  <ng-container #container></ng-container> <!-- embed view here -->
  <h3>End of Application</h3>

  <ng-template #template>
    <h1>Template Content</h1>
    <h3>Dynamically Generated!</h3>
  </ng-template>
  `
})
export class ExampleComponent implements AfterViewInit {
  @ViewChild("template", { read: TemplateRef }) tpl: TemplateRef<any>;
  @ViewChild("container", { read: ViewContainerRef }) ctr: ViewContainerRef;

  constructor() { }

  ngAfterViewInit() {
    const view =  this.tpl.createEmbeddedView(null);
    this.ctr.insert(view);
  }
}

As queries @ViewChild para o template fazem referência à variável #template. Isto fornece uma referência de template do tipo TemplateRef. TemplateRef armazena a função createEmbeddedView. Esta instancia o template como uma vista incorporada.

O único argumento de createEmbeddedView é para contexto. Se desejares passar metadados adicionais, podes fazê-lo aqui como um objeto. Os campos devem corresponder aos atributos de ng-template (let-[context-field-key-name]=“value”). Passar null indica que não são necessários mais metadados.

Uma segunda query @ViewChild fornece uma referência a ng-container como um ViewContainerRef. As vistas incorporadas são apenas anexadas a outras vistas, nunca ao DOM. O ViewContainerRef referencia a vista que recebe a vista incorporada.

Uma vista incorporada também pode ser inserida na vista de componente do <app-example></app-example>. Esta abordagem posiciona a vista no final da vista do ExampleComponent. No entanto, neste exemplo, queremos que o conteúdo apareça mesmo no meio, onde está ng-container.

A função insert do ViewContainerRef insere a vista incorporada no ng-container. O conteúdo da vista aparece no local pretendido, no centro da vista do ExampleComponent.

Redirecionamento

O redirecionamento é essencial. Muitas aplicações da web modernas hospedam demasiada informação para uma página. Os utilizadores também não devem ter de percorrer todo o conteúdo de uma página. Uma aplicação precisa de se dividir em secções distintas. Uma boa prática do Angular é carregar e configurar o redirecionamento num módulo separado, de nível superior, que é dedicado ao redirecionamento e importado pela raiz, AppModule.

Os utilizadores dão prioridade a informações necessárias. O redirecionamento ajuda-os a encontrar a secção da aplicação com tal informação. Qualquer outra informação útil para outros utilizadores pode existir num caminho totalmente diferente. Com redirecionamento, ambos os utilizadores podem encontrar o que precisam rapidamente. Detalhes irrelevantes permanecem escondidos atrás de caminhos irrelevantes.

O redirecionamento funciona muito bem ao ordenar e restringir acesso a dados da aplicação. Informação sensível nunca deve ser exibida a utilizadores não autorizados. A aplicação pode intervir entre cada caminho. Ela pode examinar a sessão de um utilizador por questões de autenticação. Este exame determina que caminho deve ser renderizado. O redirecionamento dá aos programadores a oportunidade perfeita para validar um utilizador antes de proceder.

Quanto ao Angular, o redirecionamento ocupa toda a sua própria biblioteca dentro do framework. Todos os frameworks modernos de front-end suportam redirecionamento – o Angular não é exceção. O redirecionamento acontece no lado do client utilizando quer redirecionamento de hash, quer de localização. Ambos os estilos permitem ao client gerir os seus próprios caminhos. Não é necessária assistência adicional por parte do servidor após o pedido inicial.

Redirecionamento básico

Os utilitários de redirecionamento exportam com o RouterModule disponível em @angular/router. Não faz parte da biblioteca principal, visto que nem todas as aplicações requerem redirecionamento. A forma mais convencional de introduzir redirecionamento é com o seu próprio módulo de recursos.

À medida que a complexidade cresce, ter o seu próprio módulo irá promover a simplicidade do módulo raiz. Mantê-lo estupidamente simples, sem comprometer a funcionalidade, constitui um bom design para os módulos.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AComponent } from '../../components/a/a.component';
import { BComponent } from '../../components/b/b.component';

// um array de futuros caminhos!
const routes: Routes = [];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule { }

.forRoot(...) é uma função de classe disponível a partir da classe RouterModule. A função aceita um array de objetos Route como Routes. O .forRoot(...) configura caminhos para eager-loading, enquanto que a sua alternativa, .forChild(...), configura para lazy-loading.

Eager-loading significa que os caminhos carregam o seu conteúdo para a aplicação logo no início. Lazy-loading acontece por pedido. O foco deste artigo é o eager-loading. É a abordagem predefinida para carregar uma aplicação. A definição da classe RouterModule tem um aspeto semelhante ao próximo bloco de código.

@NgModule({
  // … montes de metadados ...
})
export class RouterModule {
  forRoot(routes: Routes) {
    // … configuração para caminhos eagerly loaded …
  }

  forChild(routes: Routes) {
    // … configuração para caminhos lazily loaded …
  }
}

Não te preocupes com a configuração dos detalhes que o exemplo omite com comentários. Ter uma compreensão geral é suficiente por enquanto.

Observa como o AppRoutingModule importa o RouterModule enquanto também o exporta. Isto faz sentido visto que o AppRoutingModule é um módulo de recursos. Ele importa para o módulo raiz como um módulo de recursos. Ele expõe diretrizes RouterModule, interfaces e serviços para a árvore de componente raiz.

Isto explica a razão para o AppRoutingModule ter de exportar o RouterModule. Ele faz isto em prol da árvore de componentes subjacente da raiz do módulo. Ele precisa de ter acesso a essas funcionalidades de redirecionamento!

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { AComponent } from './components/a/a.component';
import { BComponent } from './components/b/b.component';
import { AppRoutingModule } from './modules/app-routing/app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
    AComponent,
    BComponent
  ],
  imports: [
    AppRoutingModule, // redirecionamento de módulo de funcionalidades
    BrowserModule
  ],
  providers: [],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

O token AppRoutingModule importa a partir do topo. O seu token é inserido na raiz do array de importação do módulo. A árvore de componente raiz pode agora utilizar a biblioteca RouterModule. Isto inclui as suas diretrizes, interfaces e serviços tal como já foi mencionado. Um grande agradecimento ao AppRoutingModule por exportar o RouterModule!

As funcionalidades do RouterModule vão dar jeito para os componentes da raiz. O HTML básico para o AppComponent utiliza uma diretriz: router-outlet.

<!-- app.component.html -->

<ul>
  <!-- routerLink(s) aqui -->
</ul>
<router-outlet></router-outlet>
<!-- conteúdo redirecionado é anexado aqui (DEPOIS DO ELEMENTO, NÃO DENTRO!) -->

routerLink é uma diretriz de atributo do RouterModule. Vai ser anexada a cada elemento do <ul></ul> assim que os caminhos estejam configurados. router-outlet é uma diretriz de componente com um comportamento interessante. Funciona mais como um marcador para exibir conteúdo redirecionado. O conteúdo redirecionado é resultante da navegação para um caminho específico. Geralmente, isto significa um único componente, conforme configurado no AppRoutingModule.

O conteúdo redirecionado é renderizado logo após <router-outlet></router-outlet>. Nada é renderizado dentro dele. Isto não faz grande diferença. Dito isto, não esperes que o router-outlet se comporte como um recipiente para conteúdo redirecionado. É simplesmente um marcador para anexar conteúdo redirecionado ao Modelo de Objeto de Documento (DOM).

A primeira questão a ser abordada é que caminhos esta aplicação irá consumir? Bem, existem dois componentes: AComponent e BComponent. Cada um deve ter o seu próprio caminho. Estes podem renderizar a partir do router-outlet do AppComponent, dependendo da localização atual do caminho.

A localização do caminho (ou caminhos) define o que é anexado à origem de um website (por exemplo, http://localhost:4200), através de uma série de barras (/).

// … mesmas importações de antes …

const routes: Routes = [
  {
    path: 'A',
    component: AComponent
  },
  {
    path: 'B',
    component: BComponent
  }
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule { }

http://localhost:4200/A renderiza AComponent a partir do router-outlet do AppComponent. http://localhost:4200/B renderiza BComponent. No entanto, precisas de uma maneira de encaminhar para essas localizações sem utilizar a barra de endereços. Uma aplicação não deve depender da barra de navegação de um browser para a navegação.

O CSS (do inglês, Cascading Stylesheets) global suplementa o HTML abaixo dele. Um link de redirecionamento da aplicação deve ter uma boa aparência. O CSS abaixo também é aplicado a todos os outros exemplos.

/* styles.css global */

ul li {
  cursor: pointer;
  display: inline-block;
  padding: 20px;
  margin: 5px;
  background-color: whitesmoke;
  border-radius: 5px;
  border: 1px solid black;
}

ul li:hover {
  background-color: lightgrey;
}
<!-- app.component.html -->

<ul>
  <li routerLink="/A">Vá para A!</li>
  <li routerLink="/B">Vá para B!</li>
</ul>
<router-outlet></router-outlet>

Isto é redirecionamento básico! Clicar em qualquer um dos elementos routerLink encaminha para o endereço web. Isto redefine-o sem atualizar o browser. O Router do Angular mapeia o endereço encaminhado para o Routes configurado no AppRoutingModule. Ele faz a correspondência com a propriedade path de um único objeto Route dentro do array. A primeira correspondência vence sempre, por isso, caminhos de correspondência total devem estar no final do array Routes.

Caminhos de correspondência total previnem que a aplicação pare de funcionar se não conseguir corresponder a nenhum caminho atual. Isto pode acontecer a partir da barra de endereços, onde o utilizador pode escrever outro caminho. Para isso, o Angular fornece um valor de caminho **, que aceita todos os caminhos. Esse caminho geralmente renderiza um componente PageNotFoundComponent, que exibe "Error 404: Page not found" (Erro 404: página não encontrada).

// … PageNotFoundComponent importado juntamente com todo o resto …

const routes: Routes = [
  {
    path: 'A',
    component: AComponent
  },
  {
    path: 'B',
    component: BComponent
  },
  {
    path: '',
    redirectTo: 'A',
    pathMatch: 'full'
  },
  {
    path: '**',
    component: PageNotFoundComponent
  }
];

O objeto Route que contém redirectTo impede que o PageNotFoundComponent renderize como resultado de http://localhost:4200. Este é o caminho principal da aplicação. Para corrigir isto, o redirectTo redireciona o caminho principal para http://localhost:4200/A. O http://localhost:4200/A torna-se indiretamente o novo caminho principal da aplicação.

O pathMatch: 'full' indica ao objeto Route para fazer correspondência com o caminho principal (http://localhost:4200). Isto corresponde a um caminho vazio.

Estes dois novos objetos Route ficam no final do array, visto que a primeira correspondência vence. O último elemento do array (path: '**') corresponde sempre, por isso fica no final.

Existe mais uma coisa que vale a pena abordar antes de avançarmos. Como é que o utilizador sabe onde ele ou ela está na aplicação, em relação ao caminho atual? Claro que pode existir conteúdo específico para o caminho, mas como se supõe que um utilizador fará essa ligação? Deve existir algum tipo de destaque aplicado ao routerLinks. Deste modo, o utilizador saberá que caminho está ativo para a página da web fornecida.

Esta é uma solução simples. Quando clicas num elemento routerLink, o Router do Angular atribui-lhe foco. Esse foco pode acionar determinados estilos que fornecem feedback útil ao utilizador. A diretriz routerLinkActive pode rastrear este foco para o programador.

<!-- app.component.html -->

<ul>
  <li routerLink="/A" routerLinkActive="active">Vá para A!</li>
  <li routerLink="/B" routerLinkActive="active">Vá para B!</li>
</ul>
<router-outlet></router-outlet>

A atribuição correta de routerLinkActive representa uma string de classes. Este exemplo retrata apenas uma classe (.active), mas qualquer número de classes com espaço delimitado pode ser aplicado. Quando o Router atribui foco a um routerLink, as classes de espaço delimitado são aplicadas ao elemento anfitrião. Quando o foco desaparece, as classes são removidas automaticamente.

/* styles.css global */

.active {
  background-color: lightgrey !important;
}

Os utilizadores podem agora facilmente reconhecer como o caminho atual e o conteúdo da página coincidem. O destaque lightgrey aplica-se à correspondência do routerLink com o caminho atual. !important garante que o destaque se sobrepõe a estilo em linha.