Todas as vantagens, sem complicações: um tutorial do Angular 9

Publicados: 2022-03-11

“Todo ano a internet quebra”, diz o ditado, e os desenvolvedores geralmente precisam consertá-la. Com a tão esperada versão 9 do Angular, pode-se pensar que isso se aplicaria, e os aplicativos desenvolvidos em versões anteriores precisariam passar por um grande processo de migração.

Mas esse não é o caso! A equipe Angular redesenhou completamente seu compilador, resultando em compilações mais rápidas, execuções de teste mais rápidas, tamanhos de pacotes menores e, o mais importante, compatibilidade com versões anteriores. Com o Angular 9, os desenvolvedores basicamente obtêm todas as vantagens sem nenhum incômodo.

Neste tutorial do Angular 9, construiremos um aplicativo Angular do zero. Usaremos alguns dos recursos mais recentes do Angular 9 e analisaremos outras melhorias ao longo do caminho.

Tutorial Angular 9: Começando com um novo aplicativo Angular

Vamos começar em nosso exemplo de projeto Angular. Primeiro, vamos instalar a versão mais recente da CLI do Angular:

 npm install -g @angular/cli

Podemos verificar a versão do Angular CLI executando ng version .

Em seguida, vamos criar um aplicativo Angular:

 ng new ng9-app --create-application=false --strict

Estamos usando dois argumentos em nosso comando ng new :

  • --create-application=false dirá à CLI para gerar apenas arquivos de espaço de trabalho. Isso nos ajudará a organizar melhor nosso código quando precisarmos ter mais de um aplicativo e várias bibliotecas.
  • --strict adicionará regras mais rígidas para impor mais tipagem TypeScript e limpeza de código.

Como resultado disso, temos uma pasta e arquivos básicos do espaço de trabalho.

Uma captura de tela de um IDE mostrando a pasta ng9-app, contendo node_modules, .editorconfig, .gitignore, angular.json, package-lock.json, package.json, README.md, tsconfig.json e tslint.json.

Agora, vamos adicionar um novo aplicativo. Para isso, vamos executar:

 ng generate application tv-show-rating

Seremos solicitados:

 ? Would you like to share anonymous usage data about this project with the Angular Team at Google under Google's Privacy Policy at https://policies.google.com/privacy? For more details and how to change this setting, see http://angular.io/analytics. No ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS

Agora, se executarmos ng serve , veremos o aplicativo rodando com seu scaffolding inicial.

Uma captura de tela do andaime do Angular 9, com um aviso de que "aplicativo de classificação de programa de TV está em execução!" Há também links para recursos e próximos passos.

Se executarmos ng build --prod , podemos ver a lista de arquivos gerados.

Uma captura de tela da saída "ng build --prod" do Angular 9. Ele começa com "Gerando pacotes ES5 para carregamento diferencial..." Depois disso, ele lista vários fragmentos de arquivo JavaScript - runtime, polyfills e main, cada um com uma versão -es2015 e -es5 - e um arquivo CSS. A linha final fornece um carimbo de data/hora, hash e um tempo de execução de 23.881 milissegundos.

Temos duas versões de cada arquivo. Um é compatível com navegadores legados e o outro é compilado visando o ES2015, que usa APIs mais recentes e requer menos polyfills para ser executado em navegadores.

Uma grande melhoria do Angular 9 é o tamanho do pacote. De acordo com a equipe do Angular, você pode ver uma diminuição de até 40% para grandes aplicativos.

Para um aplicativo recém-criado, o tamanho do pacote é bastante semelhante ao do Angular 8, mas à medida que seu aplicativo cresce, você verá o tamanho do pacote ficando menor em relação às versões anteriores.

Outro recurso introduzido no Angular 9 é a capacidade de nos avisar se algum dos arquivos CSS de estilo de componente for maior que um limite definido.

Uma captura de tela da seção "orçamentos" de um arquivo de configuração Angular 9 JSON, com dois objetos em uma matriz. O primeiro objeto tem "type" definido como "initial", "maximumWarning" definido como "2mb" e "maximumError" definido como "5mb". O segundo objeto tem "type" definido como "anyComponentStyle", "maximumWarning" definido como "6kb" e "maximumError" definido como "10kb".

Isso nos ajudará a capturar importações de estilos ruins ou arquivos de estilo de componentes enormes.

Adicionando um formulário para avaliar programas de TV

Em seguida, adicionaremos um formulário para avaliar programas de TV. Para isso, primeiro, vamos instalar bootstrap e ng-bootstrap :

 npm install bootstrap @ng-bootstrap/ng-bootstrap

Outra melhoria no Angular 9 é o i18n (internacionalização). Anteriormente, os desenvolvedores precisavam executar uma compilação completa para cada localidade em um aplicativo. Em vez disso, o Angular 9 nos permite criar um aplicativo uma vez e gerar todos os arquivos i18n em um processo de pós-compilação, reduzindo significativamente o tempo de compilação. Como ng-bootstrap depende do i18n, adicionaremos o novo pacote ao nosso projeto:

 ng add @angular/localize

Em seguida, adicionaremos o tema Bootstrap ao styles.scss do nosso aplicativo:

 @import "~bootstrap/scss/bootstrap";

E vamos incluir NgbModule e ReactiveFormsModule em nosso AppModule em app.module.ts :

 // ... import { ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ // ... ReactiveFormsModule, NgbModule ], })

Em seguida, atualizaremos app.component.html com uma grade básica para nosso formulário:

 <div class="container"> <div class="row"> <div class="col-6"> </div> </div> </div>

E gere o componente de formulário:

 ng gc TvRatingForm

Vamos atualizar tv-rating-form.component.html e adicionar o formulário para classificar programas de TV.

 <form [formGroup]="form" (ngSubmit)="submit()" class="mt-3"> <div class="form-group"> <label>TV SHOW</label> <select class="custom-select" formControlName="tvShow"> <option *ngFor="let tvShow of tvShows" [value]="tvShow.name">{{tvShow.name}}</option> </select> </div> <div class="form-group"> <ngb-rating [max]="5" formControlName="rating"></ngb-rating> </div> <button [disabled]="form.invalid || form.disabled" class="btn btn-primary">OK</button> </form>

E tv-rating-form.component.ts ficará assim:

 // ... export class TvRatingFormComponent implements OnInit { tvShows = [ { name: 'Better call Saul!' }, { name: 'Breaking Bad' }, { name: 'Lost' }, { name: 'Mad men' } ]; form = new FormGroup({ tvShow: new FormControl('', Validators.required), rating: new FormControl('', Validators.required), }); submit() { alert(JSON.stringify(this.form.value)); this.form.reset(); } }

Por fim, vamos adicionar o formulário a app.component.html :

 <!-- ... --> <div class="col-6"> <app-tv-rating-form></app-tv-rating-form> </div>

Neste ponto, temos algumas funcionalidades básicas de interface do usuário. Agora, se executarmos o ng serve novamente, podemos vê-lo em ação.

Uma captura de tela de um aplicativo tutorial Angular 9 mostrando um formulário intitulado "TV SHOW", com um menu suspenso listando vários títulos de programas, um medidor de estrelas e um botão OK. Na animação, o usuário seleciona um programa, seleciona uma classificação e clica no botão OK.

Antes de prosseguirmos, vamos dar uma olhada rápida em alguns novos recursos interessantes do Angular 9 que foram adicionados para ajudar na depuração. Como essa é uma tarefa muito comum no nosso dia a dia, vale a pena conhecer o que mudou para facilitar um pouco a nossa vida.

Depurando com Angular 9 Ivy

Outra grande melhoria introduzida no Angular 9 e no Angular Ivy é a experiência de depuração. O compilador agora pode detectar mais erros e lançá-los de forma mais “legível”.

Vamos vê-lo em ação. Primeiro, vamos ativar a verificação de modelo em tsconfig.json :

 { // ... "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictTemplates": true } }

Agora, se atualizarmos o array tvShows e renomearmos o name para title :

 tvShows = [ { title: 'Better call Saul!' }, { title: 'Breaking Bad' }, { title: 'Lost' }, { title: 'Mad men' } ];

…obteremos um erro do compilador.

Uma captura de tela da saída do compilador Angular 9/Angular Ivy, com um nome de arquivo e posição, dizendo "erro TS2339: A propriedade 'nome' não existe no tipo '{ title: string; }'." Ele também mostra a linha de código em questão e sublinha a referência, neste caso no arquivo tv-rating-form.component.html onde tvShow.name é mencionado. Depois disso, a referência a este arquivo HTML é rastreada para o arquivo TypeScript correspondente e destacada de forma semelhante.

Essa verificação de tipo nos permitirá evitar erros de digitação e o uso incorreto de tipos TypeScript.

Validação Angular Ivy para @Input()

Outra boa validação que recebemos é com @Input() . Por exemplo, podemos adicionar isso a tv-rating-form.component.ts :

 @Input() title: string;

…e vincule-o em app.component.html :

 <app-tv-rating-form [title]="title"></app-tv-rating-form>

… e, em seguida, altere app.component.ts assim:

 // ... export class AppComponent { title = null; }

Se fizermos essas três alterações, obteremos outro tipo de erro do compilador.

Uma captura de tela da saída do compilador Angular 9/Angular Ivy, em um formato semelhante ao anterior, destacando app.component.html com "erro TS 2322: O tipo 'null' não pode ser atribuído ao tipo 'string'."

Caso queiramos ignorá-lo, podemos usar $any() no template para converter o valor em any e corrigir o erro:

 <app-tv-rating-form [title]="$any(title)"></app-tv-rating-form>

A maneira correta de corrigir isso, no entanto, seria tornar o title no formulário anulável:

 @Input() title: string | null ;

A ExpressionChangedAfterItHasBeenCheckedError em Angular 9 Ivy

Um dos erros mais temidos no desenvolvimento Angular é o ExpressionChangedAfterItHasBeenCheckedError . Felizmente, Ivy gera o erro de maneira mais clara, tornando mais fácil encontrar de onde vem o problema.

Então, vamos apresentar um erro ExpressionChangedAfterItHasBeenCheckedError . Para fazer isso, primeiro, vamos gerar um serviço:

 ng gs Title

Em seguida, adicionaremos um BehaviorSubject e métodos para acessar o Observable e emitir um novo valor.

 export class TitleService { private bs = new BehaviorSubject < string > (''); constructor() {} get title$() { return this.bs.asObservable(); } update(title: string) { this.bs.next(title); } }

Depois disso, adicionaremos isso a app.component.html :

 <!-- ... --> <div class="col-6"> <h2> {{title$ | async}} </h2> <app-tv-rating-form [title]="title"></app-tv-rating-form> </div>

E em app.component.ts , injetaremos o TitleService :

 export class AppComponent implements OnInit { // ... title$: Observable < string > ; constructor( private titleSvc: TitleService ) {} ngOnInit() { this.title$ = this.titleSvc.title$; } // ... }

Por fim, em tv-rating-form.component.ts , injetaremos TitleService e atualizaremos o título do AppComponent , que lançará um erro ExpressionChangedAfterItHasBeenCheckedError .

 // ... constructor( private titleSvc: TitleService ) { } ngOnInit() { this.titleSvc.update('new title!'); }

Agora podemos ver o erro detalhado no console de desenvolvimento do navegador e clicar em app.component.html nos apontará para onde está o erro.

Uma captura de tela do console de desenvolvimento do navegador, mostrando o relatório de Angular Ivy do erro ExpressionChangedAfterItHasBeenCheckedError. Um rastreamento de pilha em texto vermelho fornece o erro, juntamente com os valores anteriores e atuais, e uma dica. No meio do rastreamento de pilha está a única linha que não se refere ao core.js. O usuário clica nele e é levado à linha de app.component.html que está causando o erro.

Podemos corrigir esse erro envolvendo a chamada de serviço com setTimeout :

 setTimeout(() => { this.titleSvc.update('new title!'); });

Para entender por que o erro ExpressionChangedAfterItHasBeenCheckedError acontece e explorar outras possibilidades, vale a pena ler o post de Maxim Koretskyi sobre o tópico.

Angular Ivy nos permite ter erros apresentados de forma mais clara e ajuda a impor a digitação TypeScript em nosso código. Na seção a seguir, abordaremos alguns cenários comuns em que tiraremos proveito do Ivy e da depuração.

Escrevendo um teste para nosso aplicativo Angular 9 com chicotes de componentes

No Angular 9, uma nova API de teste foi introduzida chamada de componentes de componentes . A ideia por trás disso é remover toda a tarefa necessária para interagir com o DOM, tornando-o muito mais fácil de trabalhar e mais estável para executar.

A API de componentes está incluída na biblioteca @angular/cdk , então vamos primeiro instalá-la em nosso projeto:

 npm install @angular/cdk

Agora podemos escrever um teste e aproveitar os chicotes dos componentes. Em tv-rating-form.component.spec.ts , vamos configurar o teste:

 import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { ReactiveFormsModule } from '@angular/forms'; describe('TvRatingFormComponent', () => { let component: TvRatingFormComponent; let fixture: ComponentFixture < TvRatingFormComponent > ; beforeEach(async (() => { TestBed.configureTestingModule({ imports: [ NgbModule, ReactiveFormsModule ], declarations: [TvRatingFormComponent] }).compileComponents(); })); // ... });

Em seguida, vamos implementar um ComponentHarness para nosso componente. Vamos criar dois chicotes: um para TvRatingForm e outro para NgbRating . ComponentHarness requer um campo static , hostSelector , que deve receber o valor do seletor do componente.

 // ... import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; class TvRatingFormHarness extends ComponentHarness { static hostSelector = 'app-tv-rating-form'; } class NgbRatingHarness extends ComponentHarness { static hostSelector = 'ngb-rating'; } // ...

Para nosso TvRatingFormHarness , criaremos um seletor para o botão de envio e uma função para acionar um click . Você pode ver como isso se torna mais fácil de implementar.

 class TvRatingFormHarness extends ComponentHarness { // ... protected getButton = this.locatorFor('button'); async submit() { const button = await this.getButton(); await button.click(); } }

Em seguida, adicionaremos métodos para definir uma classificação. Aqui usamos locatorForAll para procurar todos os elementos <span> que representam as estrelas nas quais o usuário pode clicar. A função rate apenas obtém todas as estrelas das classificações possíveis e clica na correspondente ao valor enviado.

 class NgbRatingHarness extends ComponentHarness { // ... protected getRatings = this.locatorForAll('span:not(.sr-only)'); async rate(value: number) { const ratings = await this.getRatings(); return ratings[value - 1].click(); } }

A última peça que falta é conectar TvRatingFormHarness a NgbRatingHarness . Para fazer isso, basta adicionar o localizador na classe TvRatingFormHarness .

 class TvRatingFormHarness extends ComponentHarness { // ... getRating = this.locatorFor(NgbRatingHarness); // ... }

Agora, vamos escrever nosso teste:

 describe('TvRatingFormComponent', () => { // ... it('should pop an alert on submit', async () => { spyOn(window, 'alert'); const select = fixture.debugElement.query(By.css('select')).nativeElement; select.value = 'Lost'; select.dispatchEvent(new Event('change')); fixture.detectChanges(); const harness = await TestbedHarnessEnvironment.harnessForFixture(fixture, TvRatingFormHarness); const rating = await harness.getRating(); await rating.rate(1); await harness.submit(); expect(window.alert).toHaveBeenCalledWith('{"tvShow":"Lost","rating":1}'); }); });

Observe que para nosso select dentro do formulário, não implementamos a configuração de seu valor por meio de um chicote. Isso ocorre porque a API ainda não suporta a seleção de uma opção. Mas isso nos dá a chance de comparar aqui como era a interação com os elementos antes dos chicotes dos componentes.

Uma última coisa antes de executarmos os testes. Precisamos corrigir app.component.spec.ts já que atualizamos title para null .

 describe('AppComponent', () => { // ... it(`should have as title 'tv-show-rating'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual(null); }); });

Agora, quando executamos ng test , nosso teste passa.

Uma captura de tela do Karma executando testes em nosso aplicativo Angular 9. Ele mostra "Ran 2 of 6 specs" com a mensagem "Incomplete: fit() ou fdescribe() foi encontrado, 2 especificações, 0 falhas, randomizado com semente 69573." Os dois testes do TvRatingFormComponent estão destacados. Os três testes do AppComponent e um teste do TitleService são todos cinza.

De volta ao nosso aplicativo de exemplo Angular 9: salvando dados em um banco de dados

Vamos encerrar nosso tutorial do Angular 9 adicionando uma conexão ao Firestore e salvando as classificações no banco de dados.

Para fazer isso, precisamos criar um projeto Firebase. Em seguida, instalaremos as dependências necessárias.

 npm install @angular/fire firebase

Nas configurações do projeto do Firebase Console, obteremos sua configuração e as adicionaremos a environment.ts e environment.prod.ts :

 export const environment = { // ... firebase: { apiKey: '{your-api-key}', authDomain: '{your-project-id}.firebaseapp.com', databaseURL: 'https://{your-project-id}.firebaseio.com', projectId: '{your-project-id}', storageBucket: '{your-project-id}.appspot.com', messagingSenderId: '{your-messaging-id}', appId: '{your-app-id}' } };

Depois disso, importaremos os módulos necessários em app.module.ts :

 import { AngularFireModule } from '@angular/fire'; import { AngularFirestoreModule } from '@angular/fire/firestore'; import { environment } from '../environments/environment'; @NgModule({ // ... imports: [ // ... AngularFireModule.initializeApp(environment.firebase), AngularFirestoreModule, ], // ... })

Em seguida, em tv-rating-form.component.ts , injetaremos o serviço AngularFirestore e salvaremos uma nova classificação no envio do formulário:

 import { AngularFirestore } from '@angular/fire/firestore'; export class TvRatingFormComponent implements OnInit { constructor( // ... private af: AngularFirestore, ) { } async submit(event: any) { this.form.disable(); await this.af.collection('ratings').add(this.form.value); this.form.enable(); this.form.reset(); } } 

Uma captura de tela de um aplicativo tutorial Angular 9 mostrando um formulário intitulado "TV SHOW" abaixo de um título de página maior "novo título!" Novamente, ele tem um menu suspenso listando alguns títulos de programas, um medidor de estrelas e um botão OK e, novamente, o usuário seleciona um programa, seleciona uma classificação e clica no botão OK.

Agora, quando formos ao Firebase Console, veremos o item recém-criado.

Uma captura de tela do Console do Firebase. Na coluna da esquerda está o joaq-lab com algumas coleções: participantes, corridas, classificações, testes e usuários. O item de classificações é selecionado e é apresentado na coluna do meio com um ID selecionado—é o único documento. A coluna da direita mostra dois campos: "rating" é definido como 4 e "tvShow" é definido como "Mad men".

Por fim, vamos listar todas as classificações em AppComponent . Para fazer isso, em app.component.ts , obteremos os dados da coleção:

 import { AngularFirestore } from '@angular/fire/firestore'; export class AppComponent implements OnInit { // ... ratings$: Observable<any>; constructor( // ... private af: AngularFirestore ) { } ngOnInit() { // ... this.ratings$ = this.af.collection('ratings').valueChanges(); } }

…e em app.component.html , adicionaremos uma lista de classificações:

 <div class="container"> <div class="row"> // ... <div class="col-6"> <div> <p *ngFor="let rating of ratings$ | async"> {{rating.tvShow}} ({{rating.rating}}) </p> </div> </div> </div> </div>

É assim que nosso aplicativo tutorial Angular 9 se parece quando tudo está pronto.

Uma captura de tela de um aplicativo tutorial Angular 9 mostrando um formulário intitulado "TV SHOW" abaixo de um título de página maior "novo título!" Novamente, ele tem um menu suspenso listando alguns títulos de programas, um medidor de estrelas e um botão OK. Desta vez, uma coluna da direita já lista "Mad men (4)", e o usuário classifica Lost com três estrelas, seguido por "Mad men" novamente com quatro estrelas. A coluna da direita permanece em ordem alfabética após as duas novas classificações.

Angular 9 e Angular Ivy: melhor desenvolvimento, melhores aplicativos e melhor compatibilidade

Neste tutorial do Angular 9, abordamos a criação de um formulário básico, salvando dados no Firebase e recuperando itens dele.

Ao longo do caminho, vimos quais melhorias e novos recursos estão incluídos no Angular 9 e no Angular Ivy. Para obter uma lista completa, você pode verificar a última postagem de lançamento do blog oficial do Angular.


Selo do Google Cloud Partner.

Como Google Cloud Partner, os especialistas certificados pelo Google da Toptal estão disponíveis para empresas sob demanda para seus projetos mais importantes.