Práticas Atemporais do Angular

Rodrigo Lima - Publicado em Sexta, 14 de junho de 2024

Introdução

Quais são algumas das melhores práticas em Angular? E por que você deve se preocupar com elas? Neste blog, discutiremos práticas atemporais que são aplicáveis a qualquer projeto Angular.

O que é uma "Melhor Prática"?

Em resumo, podemos dizer que uma Melhor Prática é uma solução geralmente aceita para um problema que cresceu organicamente ao longo de anos de tentativa e erro. Isso significa que essas práticas não foram inventadas instantaneamente. Elas começaram com uma pequena diretriz e evoluíram, tornando-se melhores ao longo do tempo com base no feedback de seus adotantes.

Elas também são claramente superiores às suas alternativas conhecidas e, além disso, as melhores práticas estão constantemente sendo desafiadas. Não apenas por seus adotantes, mas também por outras melhores práticas e metodologias.

Um bom exemplo de uma melhor prática são as WCAG ou Diretrizes de Acessibilidade para Conteúdo da Web. Elas representam as melhores práticas de acessibilidade reconhecidas globalmente na internet e são mantidas pelo W3C.

Então, que tipo de melhores práticas se aplicam ao Angular? Para deixar claro, neste post não discutiremos como organizar nosso código ou arquitetura de projeto. Não nos concentraremos em qual sistema de gerenciamento de estado usar ou nos debates atuais, como signals versus observables.

Vamos falar mais sobre alguns conceitos básicos fundamentais que abrangem qualquer tipo de configuração arquitetônica ou organização de um projeto Angular, ou até mesmo qualquer projeto web em geral:

  • Manter-se atualizado

  • Lidar com segurança

  • Criar aplicativos acessíveis

Manter-se atualizado

Manter-se atualizado às vezes é a melhor prática mais fácil. Mas potencialmente também a mais difícil, pois atualizar as dependências do seu projeto pode ser complicado!

Por exemplo, por causa das bibliotecas de terceiros que precisam seguir os ciclos de atualização do Angular. Elas precisam publicar uma nova versão antes de poderem ser atualizadas.

E ainda pior; às vezes os mantenedores abandonam bibliotecas ou perdem o foco temporariamente. Com o Angular se movendo tão rapidamente ao publicar uma nova versão a cada 6 meses, mudanças significativas podem ocorrer todos os anos. Isso exige que os mantenedores mantenham o foco e acompanhem os lançamentos do Angular.

Evitando muitas dependências de terceiros

Em geral, recomendo evitar bibliotecas de terceiros para Angular tanto quanto possível. Bem, não todas obviamente, você não pode codificar tudo sozinho. Mas especialmente aquelas que são simples bibliotecas de encapsulamento em torno de outras bibliotecas JavaScript.

Por exemplo, ng-qrcode é um encapsulamento básico em torno do pacote qrcode. Ele não faz muito mais do que apenas expor um componente e uma diretiva que passam a configuração para o qrcode.

Então, por que não usar apenas o pacote qrcode e encapsulá-lo você mesmo, se necessário? Isso fará com que você mantenha o controle total do caminho de atualização dessa dependência. Mas nada além de respeito pelo proprietário deste pacote, porque esses tipos de pacotes podem fornecer exemplos primorosos de como encapsular dependências de terceiros.

Sabendo como e quando atualizar

O conselho geral como uma melhor prática que quero transmitir aqui é garantir que você possa continuar atualizando suas dependências do Angular em tempo hábil. E se você não puder seguir imediatamente, por exemplo, devido a muitas dependências de terceiros, o Angular tem você coberto. Pelo menos se você estiver usando uma versão LTS.

Importantes lançamentos de segurança serão corrigidos em cada uma dessas versões principais que estão em LTS. Atualmente, isso é da v16 à v18, então esteja ciente de que a v15 foi recentemente removida do LTS. Isso dá a você e aos mantenedores das outras bibliotecas de terceiros que você usa, cerca de 1 a 1,5 anos para garantir que uma atualização seja feita em tempo hábil.

Ferramentas para atualizar

Então, como você pode manter as dependências do seu projeto atualizadas? Existem várias maneiras de fazer isso. Vamos considerar o gerenciador de pacotes node como um exemplo. Apenas lembre-se de que provavelmente existem ferramentas semelhantes disponíveis para yarn ou pnpm.

Se você quiser se concentrar em atualizar as dependências do Angular, você pode usar ng update através do Angular CLI. Se você usa Nx e quer migrar seu projeto Nx para as dependências mais recentes do Nx, você pode usar nx migrate latest.

Atualizando dependências relacionadas ao Angular / NX

# atualizar todas as dependências relacionadas ao Angular
npx ng update

# atualizar todas as dependências do nx
# + dependências do Angular
npx nx migrate latest

Esses 2 comandos, no entanto, se concentram apenas nas dependências relacionadas ao Angular ou Nx por padrão. Então, vamos dar um passo adiante. Se você quiser listar todas as suas dependências atuais e suas versões atuais, desejadas e mais recentes disponíveis, você pode usar o comando npm outdated. E se você quiser apenas atualizar suas dependências, use o comando npm update.

Atualizando qualquer/todas as dependências

# listar suas dependências atuais e suas
# versões atuais / desejadas / mais recentes
npm outdated

# atualizar todas as suas dependências
npm update ( --save-dev / --save )

Guia de Atualização do Angular

Claro que também temos o Guia de Atualização do Angular disponível. Você pode usar este guia para obter uma ideia inicial do trabalho que você precisa fazer para atualizar entre 2 versões principais. Cada projeto é obviamente único, então as instruções dadas aqui são apenas para dar um ponto de partida!

Segurança

Como você já deve saber, o Angular possui proteção de segurança integrada contra as vulnerabilidades mais comuns.

E certamente não é segurança através da obscuridade! Porque, obviamente, todas as partes do nosso código FE estão sempre visíveis para qualquer visitante da aplicação web.

No entanto, o Angular nos protege contra ataques como cross site scripting e cross site request forgery. E uma das melhores maneiras de se manter seguro é manter suas dependências atualizadas. Mas já cobrimos isso.

Prevenindo Cross Site Scripting (XSS)

XSS é um dos ataques mais comuns na web. Acontece quando invasores conseguem executar código injetado em suas aplicações.

O Angular trata todos os valores como não confiáveis por padrão e os sanitiza quando são injetados no DOM ou executados como parte do seu JavaScript. Então, se houver um ataque, provavelmente significa que o desenvolvedor tornou isso possível.

Se você precisar injetar conteúdo como HTML, CSS ou JS diretamente, você pode usar a classe utilitária DomSanitizer fornecida pelo Angular. Só faça isso se você tiver certeza de que está protegido contra ataques XSS por um mecanismo diferente. Por exemplo, você tem verificações em vigor para escapar de qualquer conteúdo antes de ser injetado em seu banco de dados e retornado ao navegador.

Usando o Dom Sanitizer

import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeUrl } from '@angular/platform-browser';

@Component({
  standalone: true,
  selector: 'my-component',
  template: `<div [outerHTML]="html"></div>
<script [src]="js"></script>
<a [href]="html">SamV Website</a>`,
})
export class AppComponent {
  url: SafeUrl;
  js: SafeResourceUrl;
  html: SafeHtml;
  readonly #domSanitizer = inject(DomSanitizer);

  constructor(){
    this.url = this.#domSanitizer.bypassSecurityTrustUrl('https://samv.pro');
    this.js = this.#domSanitizer.bypassSecurityTrustResourceUrl('https://samv.pro/code.js');
    this.html = this.#domSanitizer.bypassSecurityTrustHtml('SamV');
    /* E outros como
      - bypassSecurityTrustScript
      - bypassSecurityTrustStyle
     */
  }
}

Nesse caso, você pode usar bibliotecas como DOMPurify ou Sanitize HTML para fazer o trabalho pesado de sanitização para você. Cada framework moderno no espaço de back-end tem um pacote disponível para lidar com isso.

Existem outras maneiras mais detalhadas de se proteger contra esses tipos de ataques, como Política de Segurança de Conteúdo e Tipos Confiáveis.

Prevenindo Cross Site Request Forgery (XSRF)

Existem 2 vulnerabilidades no nível HTTP para as quais o Angular possui mecanismos de prevenção integrados. Cross Site Request Forgery (XSRF) e Cross Site Script Inclusion (XSSI).

Neste artigo, vou me concentrar no XSRF, pois o XSSI tornou-se irrelevante quando o Angular deixou de oferecer suporte a navegadores não modernos como o Internet Explorer. O ataque XSSI foi dificultado pelos navegadores modernos ao aplicar a política de mesma origem por padrão.

O Cross Site Request Forgery pode ser facilmente mitigado enviando um nonce do servidor em um cookie para o cliente e de volta ao servidor nos cabeçalhos da solicitação. O valor deve ser o mesmo ou a solicitação pode ser ignorada. Isso também funciona devido às políticas de mesma origem, onde outros domínios não podem ler esse cookie. Embora esta seja principalmente uma técnica do lado do servidor, o Angular possui suporte integrado para a parte do lado do cliente.

Configurando XSRF no Angular

import {
    provideHttpClient,
    withNoXsrfProtection,
    withXsrfConfiguration
} from '@angular/common/http';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      // configuração com nome de cookie/cabeçalho personalizado
      withXsrfConfiguration({
        cookieName: 'MY-XSRF-TOKEN',
        headerName: 'X-MY-XSRF-TOKEN',
      }),
      // OU; para desativá-lo completamente
      withNoXsrfProtection()
    ),
  ]
})

Basta usar a chamada da função withXsrfConfiguration para configurar o cliente HTTP durante a inicialização do seu aplicativo. Você pode fornecer seus próprios nomes de cookie e cabeçalho se desejar substituir os nomes padrão.

Comunidade de Caça a Bugs do Google

E se você encontrar um bug de segurança relacionado diretamente ao framework, você pode receber um bônus agradável. Porque o Angular faz parte do Google Open Source Software Vulnerability Reward. Portanto, se você acredita ter encontrado um problema importante, não deixe de relatá-lo!

Acessibilidade - a11y

Há muito o que dizer sobre acessibilidade na web. E, obviamente, para o Angular, isso não é muito diferente. Mas há coisas específicas a considerar ao melhorar a acessibilidade de seus aplicativos Angular.

Aplicando atributos aria

A primeira coisa que você pode fazer é elevar seus aplicativos com atributos aria. Não vou passar por toda a especificação da ARIA e seu potencial. Mas vou dar alguns exemplos.

Exemplo no Angular

<div class="toasts-wrapper" aria-live="polite">
  <div *ngFor="let toast of toasts">
    <my-toast [config]="toast"></my-toast>
  </div>
</div>

aria-live é um atributo que você pode usar para indicar aos leitores de tela que o conteúdo neste bloco provavelmente mudará ao longo do tempo ou devido à interação do usuário. Sempre que o usuário estiver ocioso, ele falará as mudanças que ocorreram nesse bloco. No exemplo acima, o contêiner de toasts contém uma lista de toasts que podem mudar devido à interação do usuário.

Outro exemplo talvez mais conhecido é o aria-label. No segundo exemplo de código abaixo, o botão não tem conteúdo textual, mas um SVG representando uma cruz para indicar que esse botão é usado para fechar algo. Um leitor de tela não pode interpretar o SVG, então o aria-label indica seu significado em texto completo.

Exemplo de aria-label e aria-hidden no Angular

<button aria-label="Fechar">
  <svg
    aria-hidden="true"
    focusable="false">
    <path ... />
  </svg>
</button>

No Angular, é importante usar o prefixo [attr.] sempre que você quiser vincular valores dinâmicos a atributos aria. Obviamente, você só precisa usar esse prefixo [attr.] se tiver valores dinâmicos. Em todos os outros casos, você pode simplesmente usar o atributo aria-label sem o prefixo.

Vinculação dinâmica

<form>
  <input type="email" required [(ngModel)]="email"
       [attr.aria-describedby]="currentError ? ('email-' + currentError) : null" />
  <div aria-live="polite">
    <div id="email-required" class="error" *ngIf="currentError === 'required'">
      O email é obrigatório
    </div>
    <div id="email-invalid" class="error" *ngIf="currentError === 'invalid'">
      O email é inválido
    </div>
  </div>
</form>

Usando o Live Announcer

Se você quiser mais controle além da marcação HTML no código do seu aplicativo para anunciar atualizações, você também pode usar o LiveAnnouncer fornecido pelo Angular CDK. O Live Announcer usa técnicas semelhantes ao atributo aria-live, mas cuidará dessa mágica para você. Você só precisa chamar sua API para garantir que o leitor de tela leia suas mensagens em voz alta.

Angular CDK Live Announcer

import { LiveAnnouncer } from '@angular/cdk/a11y';

@Component({...})
export class FormComponent {
  #liveAnnouncer = inject(LiveAnnouncer);

  public validateForm(): void {
    if (formInvalid) {
      this.liveAnnouncer.announce(
        "O formulário foi validado com os seguintes erros:..."
      );
    }
  }
}

Aumentando elementos nativos

Outro erro de implementação muito importante que eu vi várias vezes em consultoria em projetos é que os desenvolvedores tentam reinventar a roda. E isso geralmente envolve botões.

O conselho geral e a melhor prática é evitar criar seus próprios elementos se os elementos nativos já estiverem lidando com muito do trabalho árduo de acessibilidade para você no navegador. Se você deseja melhorar elementos nativos ou aumentar seu comportamento, use diretivas em vez de seus próprios componentes.

Se você gostaria de aumentar o comportamento de um botão, por exemplo, para reagir a cliques triplos e algumas variantes de estilo personalizadas, aumente o botão com uma diretiva, em vez de criar um componente personalizado que tenta imitar o comportamento completo de um botão HTML nativo.

Exemplo ruim

<my-button [disabled]="disabled"
  label="Clique aqui"
  variant="info"
  aria-role="button"
  type="button">
<

Exemplo bom

<button [disabled]="disabled"
  myButton
  variant="info"
  type="button">
   Clique aqui
</button>

Bons exemplos dessa melhor prática podem ser encontrados no projeto @angular/material. Confira a implementação dos componentes de botão ou tabela do material para descobrir como a equipe Angular faz isso.

Identificando links ativos

Outra joia escondida no pacote do roteador Angular é a diretiva ariaCurrentWhenActive. Embora esteja claramente documentada, não vi muito uso dela antes.

Indicadores de página atual

<nav>
  <a routerLink="home"
     routerLinkActive="active-page"
     ariaCurrentWhenActive="page">
    Início
  </a>
  <a routerLink="about"
     routerLinkActive="active-page"
     ariaCurrentWhenActive="page">
    Sobre
  </a>
  ...
</nav>

No exemplo acima, usei-a para indicar que os links de navegação representam a página atual quando estão ativos. No outro exemplo, abaixo, podemos indicar qual etapa está atualmente ativa ao passar por um processo passo a passo.

Indicadores de etapa atual

inject(Router).events.pipe(
  filter(e => e instanceof NavigationEnd)
).subscribe(() => {
  const content = document.querySelector('.content');
  if (content) {
    content.focus();
  }
});

Rastreamento e controle de foco

A próxima dica é sobre focar na navegação. Como desenvolvedor, você deve decidir para onde vai o foco após a navegação na página. Para conseguir isso, você pode usar o evento NavigationEnd do roteador para recuperar ou atualizar o foco.

Indicadores de etapa atual

inject(Router).events.pipe(
  filter(e => e instanceof NavigationEnd)
).subscribe(() => {
  const content = document.querySelector('.content');
  if (content) {
    content.focus();
  }
});

O objetivo dessa técnica é evitar o foco no primeiro elemento do corpo novamente e garantir que seu conteúdo carregado seja utilizável imediatamente.

E para permanecer no contexto do foco, outro dos meus favoritos é prender o foco. Com esta simples diretiva cdkTrapFocus, é fácil manter o foco limitado ao contexto, por exemplo, ao abrir um diálogo ou um flyout de detalhes.

Prender o foco

<div class="my-inner-dialog-content" cdkTrapFocus>
  <!-- Tab e Shift + Tab não sairão deste elemento. -->
</div>

Se você quiser mais controle, o Angular CDK também expõe diretivas para marcar especificamente o fim e o início de uma região de foco e para especificar qual elemento deve receber o foco inicial.

Mais controles de foco

<nav class="pagination">
  <a href="#start" cdkFocusRegionStart>Início</a>
  <a href="#previous">Anterior</a>
  <a href="#current" cdkFocusInitial>Atual</a>
  <a href="#next">Próximo</a>
  <a href="#end" cdkFocusRegionEnd>Fim</a>
</nav>

Conclusão

Neste artigo, discutimos 3 grandes tópicos de melhores práticas no Angular, ou em geral aplicáveis a qualquer projeto web. O primeiro é garantir que você possa continuar atualizando seus aplicativos para acompanhar a versão LTS do Angular. Ao não fazer isso, você pode perder importantes atualizações de segurança.

Além disso, existem alguns recursos de segurança integrados no framework, como sanitização automática de conteúdo e proteção contra as ameaças de segurança mais conhecidas ao comunicar dados pela internet, como XSRF e XSS.

Finalmente, aprendemos sobre maneiras de otimizar nossos aplicativos para todos os usuários e torná-los acessíveis para a maioria aplicando atributos aria e controlando o foco quando necessário.

  1. Migrations in NX

  2. NPM outdated

  3. NPM update

  4. WCAG 2 Overview

  5. Why is there a caret in my package.json?

  6. ARIA Live regions

  7. Augmenting Native Elements