Boas práticas para organizar seu front-end: Exemplos com Angular - Parte II

Boas práticas para organizar seu front-end: Exemplos com Angular - Parte II

O artigo anterior, Boas práticas para organizar seu front-end: Exemplos com o framework Angular - Parte I, trouxe práticas importantes que podemos definir em nossos projetos. É essencial conhecer e colocar sempre que possível em prática. O foco deste artigo é apresentar conceitos que serão úteis para o desenvolvimento de aplicações utilizando Angular e TypeScript. Vamos começar!

Lazy Loading (carregamento lento)

Se você ainda não usa dentro do Framework Angular o carregamento lento (por demanda) deve começar a considerar o uso desse recurso o mais rápido possível!

O carregamento lento é o processo de carregar componentes, módulos ou outros recursos de um site (aplicativo) conforme necessário. Se não está utilizando, todos os seus componentes são carregados de uma vez. Isso significa que muitas bibliotecas ou módulos estão sendo carregados sem mesmo o usuário acessar esses recursos da sua aplicação. Se a aplicação não tem muitos recursos, ou seja é bem simples e pequena, talvez isso não afete no desempenho ao carregar no browser do seu cliente. Mas se ela está crescendo muito, isso com certeza vai causar muitos problemas, o tempo de carregamento aumentará a partir do momento que tiver muitos arquivos a descarregar no browser.

Se você já trabalha alguns meses com o Angular já sabe como colocar em prática. Mas mesmo assim vamos analisar rapidamente com um exemplo.

Antes precisamos ter o componente criado e declarar esse componente em um módulo exclusivo dele. Então se você está desenvolvendo o componente de account, terá um módulo chamado account.module.

Para fazer isso, podemos criar rotas separadas para cada sub-módulo ou podemos definir as rotas no app-routing.module.ts:

{
    path: 'account',
    loadChildren: () => import('./account/account.module')
      .then(mod => mod.AccountModule)
  },

Quando a rota account, é chamada pelo cliente, o framework carrega o módulo exportado padrão e suas importações relacionadas e os componentes em vez de carregar todos as outras rotas que a sua aplicação pode ter, resultando em um tamanho de pacote inicial menor e isso traz muitas vantagens para sua aplicação e também para o cliente.

Depois de realizar isso basta fornecer a saída para que os componentes possam ser renderizados por demanda. Para isso declaramos no componente raiz de toda aplicação Angular o router-outlet:

// app.component.html
<router-outlet></router-outlet>

Por isso incentivo que coloque em prática em seus projetos que utilizam o Angular.

Propriedades em seu componente

É uma sugestão que pode deixar todos seus componentes com um padrão. Em algumas aplicações grandes que não tiveram o devido cuidado e organização, é possível encontrar as propriedades e métodos espalhados e fora de ordem, dessa maneira:

private supplier: string;
public address;
entityUser;
readonly dateStart
returnUrlForId;

As propriedades que esse componente possui estão bem desorganizadas. Por isso adote desde o inicio um padrão. Segue uma sugestão de como organizar seu componente tanto a ordem das propriedades e a de métodos:

  • Propriedades públicas são declaradas primeiro.
  • Propriedades Privadas sempre devem vir em segundo.
  • Construtor caso precise. Busque sempre injetar o que apenas realmente vai usar.
  • Métodos públicos fica mais fácil visualizar esses métodos.
  • Métodos privados é comum que eles sejam usados nos métodos públicos então podem ficar após todas as declarações.

Princípio da Responsabilidade Única

Podemos sim colocar em prática este princípio em aplicações front-end, basta entender que um componente deve ter uma única responsabilidade bem definida.

Se está criando um componente que vai realizar o login do usuário, faria sentido ter outras responsabilidades além disso? Obvio que não. É importante entender o contexto do Software e saber separar bem seus componentes no front-end. E para métodos isso também se aplica? Com certeza! Veja um exemplo clássico do que nunca fazer:

createUpdate(product: Product): Observable<Product> {
    if (this.baseUrl === 'create') {
      return this.http.post<Product>(this.baseUrl, product).pipe(
        map((obj) => obj),
        catchError((e) => this.errorHandler(e))
      );
    } 
      return this.http.put<Product>(url, product).pipe(
        map((obj) => obj),
        catchError((e) => this.errorHandler(e))
      );
  }

O exemplo acima era muito visto em algumas aplicações. O que está errado? Claramente temos dois tipos de métodos de requisição, em uma mesma função! A probabilidade de bugs é muito alta! Fazer isso não economiza linhas, apenas atrapalha quem está iniciando e o próximo desenvolvedor que for fazer manutenção nesse código! Veja o código a seguir, depois de um refactoring:

 createProduct(product: Product): Observable<Product> {
    return this.http.post<Product>(this.baseUrl, product).pipe(
      map((obj) => obj),
      catchError((e) => this.errorHandler(e))
    );
  }

 updateProduct(product: Product): Observable<Product> {
    return this.http.put<Product>(this.baseUrl, product).pipe(
      map((obj) => obj),
      catchError((e) => this.errorHandler(e))
    );
  }

Isso é manter as responsabilidades separadas em funções também! Tudo deve ter apenas uma responsabilidade e deve ser pensado para ser escrito da maneira mais simples e eficiente possível! Lembre que não é apenas as classes que devem ter um único objetivo, mas funções (métodos) estão incluídos:

" Single Responsibility Principle - uma classe ou função deve ter apenas um motivo para mudar. "

Se uma classe só deve ter um motivo para ser modificada, certamente ela só deve ter uma única responsabilidade.

Veja o que a documentação do Angular diz sobre este assunto:

" Crie serviços com uma única responsabilidade que é encapsulada por seu contexto (regras).

Crie um novo serviço assim que o serviço começar a exceder aquele propósito singular.

Por quê? Quando um serviço tem várias responsabilidades, torna-se difícil testá-lo.

Por quê? Quando um serviço tem múltiplas responsabilidades, cada componente ou serviço que o injeta agora carrega o peso de todos eles. "

Faça uso de Enums

É comum encontrar sugestões de declarar dentro do componente propriedades do tipo String, com mais de uma atribuição de valores, segue um exemplo:

private TipoDeVeiculo: 'carro' | 'moto';

Se você tentar atribuir outro valor que seja diferente de Carro e Moto, vai receber naturalmente um erro:

Captura de Tela 2021-12-16 às 13.07.43.png

Declarar no componente não é um erro. Mas você tem outras alternativas bem mais elegantes. Podemos utilizar enums para isso. Você pode criar um arquivo tipos-de-veiculos.enum.ts e fazer uso dele em seu componente:

// tipos-de-veiculos.enum.ts
export enum TiposDeVeiculos{
Carro = 'Carro',
Moto = 'Moto'
}

Depois poderia importar em seu componente e utiliza-lo para o fim que desejar:

 public tipoVeiculo = 'Onibus'; // Tipo de veiculo que foi escolhido pelo usuário.

  ngOnInit() {
    if (this.tipoVeiculo === TipoDeVeiculos.Carro) {
      console.log(true);
    } else {
      console.log(false);
    }
  }

O código acima serve como um exemplo bem simples de como utilizar o Enum pode deixar o código mais claro de se ler e elegante. Além disso se for necessário podemos utilizar o enum no template, mas apenas faça isso se for realmente necessário. Tente sempre utiliza-lo dentro do seu componente.

Sempre tenha um módulo de compartilhamento (Shared Module)

Você pode criar um módulo compartilhado que tenha todos componentes, diretivas e pipes que podem ser reutilizados. Isso significa que os nossos módulos específicos que tem uma responsabilidade bem clara, podem usar esses componentes do Shared. Isso ajuda muito a manter toda aplicação no front-end, limpa, organizada, com menos imports e códigos repetidos! Implementar o Shared Module é aplicar um princípio importante o DRY (Don't repeat yourself).

Segue uma forma de utilizar o Shared Module :

Captura de Tela 2021-12-16 às 14.53.00.png

Reparem que temos uma pasta shared e dentro uma pasta components. Nela temos componentes que podemos utilizar em outros lugares da nossa aplicação, evitando repetir código. Dentro da pasta shared temos o shared.module.ts, nessa mesma aplicação existe outro módulo que se chama shop.module.ts. Se quisermos que o módulo de shop enxergue todos os recursos temos que declarar o shared dentro do ShopModule, conforme a imagem mostra:

Captura de Tela 2021-12-16 às 15.11.38.png

Assim o componentes que o ShopModule possuem podem acessar todos os recursos (componentes) do SharedModule, evitando a repetição de código. Espero que tenha ficado claro 🙂.

Conclusão

Organizar o front-end é muito importante! Principalmente quando já se tem noção das proporções que o Software está ganhando com a adição de novas features.

Fico por aqui neste post. Obrigado por ler. Até! 😄 🚀