Boas Práticas para um Código Elegante
Em busca de um guia de estilo opinativo para sintaxe, convenções e estrutura de aplicações Angular?
Bem-vindo a bordo!
Este guia apresenta as convenções preferidas e, mais importante, explica o porquê. Assim como um mestre artesão esculpe sua obra-prima, vamos explorar as melhores práticas para criar um código Angular elegante, manutenível e de alta qualidade.
O Vocabulário do Estilo Angular
Cada diretriz deste guia descreve uma prática boa ou ruim, todas com uma apresentação consistente. A força de cada recomendação é indicada pela escolha das palavras:
- ✅“Faça” (Do): Uma prática que deve ser sempre seguida. “Sempre” pode ser uma palavra forte demais, mas você precisará de um caso excepcional para quebrar uma diretriz “Faça”.
- 💡“Considere” (Consider): Diretrizes que geralmente devem ser seguidas. Se você entende o significado por trás da diretriz e tem um bom motivo para se desviar, faça-o. Busque a consistência.
- ❌”Evite” (Avoid): Indica algo que você quase nunca deve fazer. Exemplos de código a serem evitados têm um inconfundível cabeçalho vermelho.
Após cada recomendação, o ❓”Por quê?” oferece as razões para seguir as diretrizes, assim como um mestre explica suas técnicas a um aprendiz.
Convenções da Estrutura de Arquivos: A Organização da Oficina
Alguns exemplos de código exibem um arquivo que possui um ou mais arquivos complementares com nomes semelhantes. Por exemplo, hero.component.ts
, hero.component.html
, etc.
Para simplificar, usaremos o atalho hero.component.ts|html|css|spec
para representar esses vários arquivos. Essa abreviação torna a leitura e a compreensão das estruturas de arquivos deste guia mais concisa e clara.
Responsabilidade Única: Cada Ferramenta com seu Propósito
Aplique o princípio da responsabilidade única (SRP) a todos os componentes, serviços e outros símbolos. Assim como cada ferramenta em uma oficina tem um propósito específico, cada elemento do seu código deve ter uma única responsabilidade bem definida. Isso torna a aplicação mais limpa, legível, manutenível e fácil de testar.
Em nossa jornada, exploraremos cada um desses tópicos em detalhes, desvendando os segredos da maestria em Angular e construindo aplicações que são verdadeiras obras de arte da engenharia de software.
✅Bom Exemplo de Responsabilidade Única:
// Componente de nave estelar
@Component({
selector: 'app-nave-estelar',
templateUrl: './nave-estelar.component.html',
styleUrls: ['./nave-estelar.component.css']
})
export class NaveEstelarComponent {
@Input() velocidade: number;
@Output() viagemConcluida = new EventEmitter<void>();
iniciarViagem() {
console.log('A viagem começou!');
// lógica para iniciar a viagem
}
}
❌Mau Exemplo de Responsabilidade Única:
// Componente de nave estelar com responsabilidade múltipla
@Component({
selector: 'app-nave-estelar',
templateUrl: './nave-estelar.component.html',
styleUrls: ['./nave-estelar.component.css']
})
export class NaveEstelarComponent {
@Input() velocidade: number;
@Output() viagemConcluida = new EventEmitter<void>();
iniciarViagem() {
console.log('A viagem começou!');
// lógica para iniciar a viagem
}
calcularTrajetoria() {
console.log('Calculando trajetória...');
// lógica para calcular a trajetória
}
gerenciarCombustivel() {
console.log('Gerenciando combustível...');
// lógica para gerenciar combustível
}
}
No bom exemplo, o componente NaveEstelarComponent
tem uma única responsabilidade: gerenciar a nave estelar. Ele não lida com cálculos de trajetória ou gerenciamento de combustível, o que deve ser feito por outros serviços ou componentes dedicados. Isso torna o código mais modular e fácil de manter.
A Regra do Um: A Unidade Fundamental da Arquitetura Angular
Assim como um mestre artesão organiza sua oficina com cada ferramenta em seu devido lugar, o princípio da “Regra do Um” defende que cada elemento do seu código Angular – um serviço, um componente, etc. – deve residir em seu próprio arquivo. Essa prática, embora simples, traz benefícios significativos para a legibilidade, manutenção e colaboração em projetos Angular.
✅Faça: Defina um único elemento, como um serviço ou componente, por arquivo.
💡Considere: Limitar os arquivos a 400 linhas de código.
❓Por quê?
- Legibilidade Aprimorada: Um componente por arquivo facilita a leitura e a compreensão do código, assim como um livro bem organizado com capítulos distintos.
- Manutenção Simplificada: Isolar componentes em arquivos individuais torna a manutenção mais fácil, pois as alterações em um componente não afetam outros.
- Colaboração Eficaz: Em projetos com vários desenvolvedores, ter um componente por arquivo reduz o risco de conflitos no controle de versão, como o Git.
- Prevenção de Bugs Ocultos: Combinar componentes em um único arquivo pode levar a erros sutis, como compartilhamento de variáveis, criação de closures indesejados e acoplamento indesejado com dependências.
- Lazy Loading Facilitado: Um único componente por arquivo pode ser a exportação padrão, o que simplifica o carregamento sob demanda (lazy loading) com o roteador Angular.
❌Exemplo de Violação da Regra do Um: Uma Oficina Desorganizada
/* Evite */
import {Component, NgModule, OnInit} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
interface Hero {
id: number;
name: string;
}
@Component({
selector: 'app-root',
template: `
<h1>{{title}}</h1>
<pre>{{heroes | json}}</pre>
`,
styleUrls: ['../app.component.css'],
})
export class AppComponent implements OnInit {
title = 'Tour of Heroes';
heroes: Hero[] = [];
ngOnInit() {
getHeroes().then((heroes) => (this.heroes = heroes));
}
}
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
exports: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule);
const HEROES: Hero[] = [
{id: 1, name: 'Bombasto'},
{id: 2, name: 'Tornado'},
{id: 3, name: 'Magneta'},
];
function getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES); // TODO: get hero data from the server;
}
Este exemplo negativo define o AppComponent
, inicializa a aplicação, define o modelo Nave
e carrega dados do servidor, tudo no mesmo arquivo. É como uma oficina onde ferramentas, materiais e projetos estão misturados, dificultando o trabalho e aumentando o risco de erros.
✅Exemplo Positivo: A Organização da Regra do Um
// app/heroes/hero.model.ts
export interface Hero {
id: number;
name: string;
}
// app/heroes/hero.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero.model';
const HEROES: Hero[] = [
{id: 1, name: 'Bombasto'},
{id: 2, name: 'Tornado'},
{id: 3, name: 'Magneta'},
];
@Injectable({
providedIn: 'root'
})
export class HeroService {
getHeroes(): Promise<Hero[]> {
return Promise.resolve(HEROES); // TODO: get hero data from the server;
}
}
// app/heroes/hero.component.ts
import { Component, OnInit } from '@angular/core';
import { HeroService } from './hero.service';
import { Hero } from './hero.model';
@Component({
selector: 'app-hero',
templateUrl: './hero.component.html',
styleUrls: ['./hero.component.css']
})
export class HeroComponent implements OnInit {
title = 'Tour of Heroes';
heroes: Hero[] = [];
constructor(private heroService: HeroService) {}
ngOnInit() {
this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
}
// app/heroes/hero.component.html
<h1>{{ title }}</h1>
<pre>{{ heroes | json }}</pre>
// app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HeroComponent } from './heroes/hero.component';
@NgModule({
declarations: [HeroComponent],
imports: [BrowserModule],
bootstrap: [HeroComponent]
})
export class AppModule {}
// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
No exemplo refatorado, cada responsabilidade está claramente separada em seu próprio arquivo. HeroComponent
é responsável apenas pela exibição e interação com o usuário, HeroService
gerencia a lógica de obtenção de dados dos heróis, e Hero
é um modelo simples. Isso torna o código mais modular, mais fácil de testar e manter, além de facilitar a colaboração em equipe sem conflitos no controle de versão.
A prática recomendada é distribuir o componente e suas classes auxiliares em arquivos dedicados, como mostrado acima. Essa organização modular, semelhante a uma oficina bem estruturada, promove a reutilização do código, facilita a leitura e reduz a probabilidade de erros.
À medida que a aplicação cresce, a importância da Regra do Um se torna ainda mais evidente. Ao adotar essa prática, você estará construindo uma base sólida para o seu projeto Angular, garantindo um código mais limpo, manutenível e escalável.
A Nomenclatura Angular: A Linguagem da Clareza
Na arte da arquitetura Angular, a nomenclatura desempenha um papel crucial. Assim como um mestre artesão nomeia suas ferramentas e materiais com precisão, a escolha dos nomes certos para arquivos, componentes, serviços e outros elementos é fundamental para a legibilidade e manutenção do código.
Diretrizes Gerais de Nomenclatura: A Harmonia da Consistência
✅Faça: Use nomes consistentes para todos os símbolos.
✅Faça: Siga um padrão que descreva a funcionalidade do símbolo e, em seguida, seu tipo. O padrão recomendado é funcionalidade.tipo.ts
.
❓Por quê?
- Consistência é Chave: Convenções de nomenclatura fornecem uma maneira uniforme de encontrar conteúdo rapidamente. A consistência dentro do projeto é vital, e a consistência entre equipes e empresas aumenta a eficiência.
- Facilidade de Busca: As convenções de nomenclatura devem ajudar a encontrar o código desejado mais rapidamente e torná-lo mais fácil de entender.
- Clareza de Propósito: Nomes de pastas e arquivos devem transmitir claramente sua intenção. Por exemplo,
app/herois/lista-herois.component.ts
pode conter um componente que gerencia uma lista de heróis.
Em nossa jornada, exploraremos convenções de nomenclatura específicas para diferentes elementos do Angular, como componentes, serviços, diretivas e pipes. Ao adotar essas diretrizes, você estará construindo um código mais legível, compreensível e fácil de manter.
Separando Nomes de Arquivos com Pontos e Traços: A Clareza na Organização
Na tapeçaria da estrutura de arquivos Angular, a escolha dos separadores certos contribui para a legibilidade e a organização. Assim como um mestre artesão separa suas ferramentas com cuidado, este guia recomenda o uso de traços e pontos para separar palavras e tipos em nomes de arquivos.
✅Faça: Use traços para separar palavras no nome descritivo.
✅Faça: Use pontos para separar o nome descritivo do tipo.
✅Faça: Use nomes de tipo consistentes para todos os componentes, seguindo um padrão que descreva a funcionalidade do componente e, em seguida, seu tipo. Um padrão recomendado é funcionalidade.tipo.ts
.
✅Faça: Use nomes de tipo convencionais, incluindo .service
, .component
, .pipe
, .module
e .directive
. Invente nomes de tipo adicionais se necessário, mas evite criar muitos.
❓Por quê?
- Identificação Rápida: Os nomes de tipo fornecem uma maneira consistente de identificar rapidamente o conteúdo do arquivo.
- Busca Eficiente: Os nomes de tipo facilitam a busca por um tipo de arquivo específico usando as técnicas de pesquisa difusa de um editor ou IDE.
- Clareza e Descrição: Nomes de tipo não abreviados, como
.service
, são descritivos e inequívocos. Abreviações como.srv
,.svc
e.serv
podem ser confusas. - Padronização para Automação: Os nomes de tipo fornecem um padrão consistente para qualquer tarefa automatizada.
Exemplos:
lista-herois.component.ts
heroi.service.ts
filtro-nome.pipe.ts
herois.module.ts
destaque.directive.ts
Ao adotar essas convenções, você estará tecendo uma estrutura de arquivos clara e organizada, facilitando a navegação e a compreensão do seu projeto Angular.
Símbolos e Nomes de Arquivos: A Consistência como Guia
Na sinfonia do código Angular, a harmonia entre os nomes dos símbolos e seus respectivos arquivos é essencial. Assim como um maestro coordena cada instrumento da orquestra, este guia estabelece diretrizes para manter a coerência entre os nomes dos elementos e seus arquivos.
✅Faça: Use nomes consistentes para todos os ativos, nomeando-os de acordo com o que representam.
✅Faça: Use PascalCase
(maiúsculas no início de cada palavra) para nomes de classes.
✅Faça: Combine o nome do símbolo com o nome do arquivo.
✅Faça: Acrescente ao nome do símbolo o sufixo convencional (como Component
, Directive
, Module
, Pipe
ou Service
) para elementos desse tipo.
✅Faça: Dê ao nome do arquivo o sufixo convencional (como .component.ts
, .directive.ts
, .module.ts
, .pipe.ts
ou .service.ts
) para arquivos desse tipo.
❓Por quê?
- Convenções Facilitam a Identificação: Convenções consistentes facilitam a identificação rápida e a referência a ativos de diferentes tipos.
- Organização e Clareza: A combinação entre nomes de símbolos e arquivos torna o código mais organizado e compreensível.
Exemplos de Nomes de Símbolos e Arquivos
// Símbolo: AppComponent
// Nome do Arquivo: app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {}
// Símbolo: HeroesComponent
// Nome do Arquivo: heroes.component.ts
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent {}
// Símbolo: HeroListComponent
// Nome do Arquivo: hero-list.component.ts
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent {}
// Símbolo: HeroDetailComponent
// Nome do Arquivo: hero-detail.component.ts
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent {}
// Símbolo: ValidationDirective
// Nome do Arquivo: validation.directive.ts
@Directive({
selector: '[appValidation]'
})
export class ValidationDirective {}
// Símbolo: AppModule
// Nome do Arquivo: app.module.ts
@NgModule({
declarations: [
AppComponent,
HeroesComponent,
HeroListComponent,
HeroDetailComponent,
ValidationDirective
],
imports: [BrowserModule],
bootstrap: [AppComponent]
})
export class AppModule {}
// Símbolo: InitCapsPipe
// Nome do Arquivo: init-caps.pipe.ts
@Pipe({ name: 'initCaps' })
export class InitCapsPipe implements PipeTransform {
transform(value: string): string {
return value.replace(/\b\w/g, first => first.toLocaleUpperCase());
}
}
// Símbolo: UserProfileService
// Nome do Arquivo: user-profile.service.ts
@Injectable({
providedIn: 'root'
})
export class UserProfileService {
getUserProfile(): UserProfile {
// lógica para obter perfil do usuário
}
}
Seguir convenções de nomenclatura consistentes para símbolos e nomes de arquivos ajuda a criar um código mais organizado, legível e fácil de manter.
Cada símbolo deve ter um nome que corresponda ao nome do arquivo e deve incluir o sufixo convencional apropriado para o tipo de símbolo.
Nomes de Serviços: Identificando os Agentes da Aplicação
No universo Angular, os serviços são como agentes especializados, cada um com um papel fundamental na execução das tarefas da aplicação. Assim como um mestre artesão identifica cada um de seus aprendizes por seus nomes e funções, este guia estabelece convenções para nomear serviços de forma clara e consistente.
✅Faça: Use nomes consistentes para todos os serviços, nomeando-os de acordo com sua funcionalidade.
✅Faça: Adicione o sufixo “Service” ao nome da classe do serviço. Por exemplo, um serviço que obtém dados ou heróis deve ser chamado de DataService
ou HeroService
.
💡Exceção: Alguns termos já indicam claramente que se trata de um serviço, como aqueles que terminam em “-er” (por exemplo, “Logger”). Nesses casos, você pode optar por não usar o sufixo “Service”. Decida se essa exceção é aceitável em seu projeto e, como sempre, busque a consistência.
❓Por quê?
- Identificação Rápida: O sufixo “Service” fornece uma maneira consistente de identificar e referenciar serviços rapidamente.
- Clareza e Consistência: Nomes de serviço claros, como
Logger
, não requerem um sufixo. - Desambiguação: Nomes de serviço que são substantivos, como
Credito
, exigem um sufixo para indicar que se trata de um serviço e não de outro elemento.
Exemplos:
// Símbolo: HeroDataService
// Nome do Arquivo: hero-data.service.ts
@Injectable({
providedIn: 'root'
})
export class HeroDataService {
getHeroes(): Hero[] {
// lógica para obter heróis
}
}
// Símbolo: CreditService
// Nome do Arquivo: credit.service.ts
@Injectable({
providedIn: 'root'
})
export class CreditService {
checkCredit(): CreditStatus {
// lógica para verificar crédito
}
}
// Símbolo: Logger
// Nome do Arquivo: logger.service.ts
@Injectable({
providedIn: 'root'
})
export class Logger {
log(message: string): void {
console.log(message);
}
}
Ao seguir estas diretrizes, você estará nomeando seus serviços de forma clara e consistente, facilitando a comunicação e a colaboração em seu projeto Angular. Cada serviço será como um agente bem identificado, pronto para desempenhar seu papel na sinfonia da sua aplicação.
Bootstrapping: A Centelha da Aplicação Angular
No coração de cada aplicação Angular, o processo de bootstrapping é a faísca que inicia a execução. Assim como um mestre artesão acende a forja para iniciar seu trabalho, o bootstrapping prepara o ambiente para a aplicação Angular ganhar vida.
✅Faça: Coloque a lógica de bootstrapping e plataforma da aplicação em um arquivo chamado main.ts
.
✅Faça: Inclua tratamento de erros na lógica de bootstrapping.
❌Evite: Colocar a lógica da aplicação em main.ts
. Em vez disso, considere colocá-la em um componente ou serviço.
❓Por quê?
- Convenção Consistente: Seguir uma convenção consistente para a lógica de inicialização de um aplicativo facilita a compreensão e a manutenção.
- Familiaridade: Essa convenção é familiar em outras plataformas de tecnologia, tornando-a mais fácil de entender para desenvolvedores de diferentes origens.
Exemplo: main.ts
// Arquivo: main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
// Tratamento de erros na inicialização
try {
bootstrapApplication(AppComponent);
} catch (err) {
console.error('Erro ao inicializar a aplicação:', err);
}
Neste exemplo, o arquivo main.ts
importa o componente principal (AppComponent
) e usa a função bootstrapApplication
para inicializar a aplicação Angular. O tratamento de erros pode ser adicionado para garantir a robustez da inicialização.
Ao seguir essa convenção, você estará estabelecendo um ponto de partida claro e consistente para sua aplicação Angular. O arquivo main.ts
se tornará a porta de entrada para o mundo da sua aplicação, pronta para receber os usuários e executar as funcionalidades que você criou.
Component Selectors: A Identidade dos Componentes
Na galeria de componentes Angular, cada um possui um seletor único que o identifica no template HTML. Assim como um mestre artesão marca suas criações com um selo distintivo, o seletor do componente permite que ele seja usado e referenciado em outros lugares da aplicação.
Faça: Use dashed-case
ou kebab-case
(letras minúsculas separadas por traços) para nomear os seletores de elementos de componentes.
Por quê? Mantém os nomes dos elementos consistentes com a especificação para Elementos Personalizados (Custom Elements), garantindo a interoperabilidade com outras tecnologias web.
❌Exemplo Incorreto:
// Evite!
@Component({
standalone: true,
selector: 'tohHeroButton', // Não recomendado (camelCase)
templateUrl: './hero-button.component.html',
})
export class HeroButtonComponent {}
✅Exemplo Correto:
@Component({
standalone: true,
selector: 'toh-hero-button', // Recomendado (kebab-case)
templateUrl: './hero-button.component.html',
})
export class HeroButtonComponent {}
No exemplo correto, o seletor do componente HeroButtonComponent
está em kebab-case
(toh-hero-button
), seguindo a convenção recomendada e garantindo a compatibilidade com a especificação de Elementos Personalizados.
Ao adotar essa convenção, você estará atribuindo uma identidade clara e consistente aos seus componentes Angular, facilitando a leitura do código e a integração com outras partes da aplicação.
Prefixo Personalizado para Componentes: Organizando a Coleção
Na vasta coleção de componentes de uma aplicação Angular, a organização é fundamental. Assim como um mestre artesão organiza suas obras por temas ou estilos, o uso de prefixos personalizados para os seletores de componentes permite agrupar e identificar facilmente elementos relacionados.
Faça: Use um seletor de elemento com letras minúsculas e separadas por hífen, por exemplo, admin-usuarios
.
Faça: Use um prefixo que identifique a área funcional ou a própria aplicação.
Por quê?
- Prevenção de Conflitos: Evita colisões de nomes de elementos com componentes de outras aplicações ou com elementos HTML nativos.
- Facilita o Compartilhamento: Torna mais fácil promover e compartilhar o componente em outras aplicações, pois o prefixo indica sua origem.
- Identificação no DOM: Componentes são facilmente identificados no DOM (Document Object Model) pela presença do prefixo.
❌Exemplo Incorreto:
// Evite!
// HeroiComponent pertence à funcionalidade "Tour dos Heróis"
@Component({
standalone: true,
selector: 'heroi', // Sem prefixo
template: '',
})
export class HeroiComponent {}
// UsuariosComponent pertence à funcionalidade "Admin"
@Component({
standalone: true,
selector: 'usuarios', // Sem prefixo
template: '',
})
export class UsuariosComponent {}
✅Exemplo Correto:
@Component({
standalone: true,
selector: 'toh-heroi', // Prefixo "toh" para "Tour dos Heróis"
template: '',
})
export class HeroiComponent {}
@Component({
standalone: true,
selector: 'admin-usuarios', // Prefixo "admin"
template: '',
})
export class UsuariosComponent {}
No exemplo correto, os componentes recebem prefixos que indicam suas respectivas áreas funcionais (toh
para “Tour dos Heróis” e admin
para a área de administração). Isso evita conflitos de nomes e torna os componentes facilmente identificáveis no código e no DOM.
Ao adotar essa prática, você estará organizando seus componentes como um mestre artesão organiza sua coleção, facilitando a gestão e o reuso em diferentes contextos.
Seletores de Diretivas: A Sutileza da Manipulação
Em Angular, as diretivas são como pincéis delicados, adicionando comportamentos e estilos aos elementos do DOM. Assim como um mestre artesão escolhe seus pincéis com precisão, a nomenclatura dos seletores de diretivas exige atenção aos detalhes.
✅Faça: Use camelCase
(primeira letra minúscula, demais maiúsculas no início de cada palavra) para nomear os seletores de diretivas.
❓Por quê?
- Consistência com Atributos: Mantém os nomes das propriedades definidas nas diretivas, que se ligam à visualização, consistentes com os nomes dos atributos.
- Case-Sensitive: O analisador HTML do Angular diferencia maiúsculas de minúsculas e reconhece o
camelCase
.
Prefixo Personalizado para Diretivas: Evitando a Confusão
Assim como um mestre artesão marca seus pincéis para evitar trocas acidentais, o uso de prefixos personalizados para diretivas ajuda a prevenir conflitos de nomes.
✅Faça: Use camelCase
para seletores que não são elementos, a menos que o seletor deva corresponder a um atributo HTML nativo.
❌Evite: Use o prefixo ng
para nomes de diretivas, pois esse prefixo é reservado para o Angular e usá-lo pode causar bugs difíceis de diagnosticar.
❓Por quê?
- Prevenção de Conflitos: Evita colisões de nomes com diretivas de outras bibliotecas ou com atributos HTML nativos.
- Identificação Facilitada: As diretivas são facilmente identificadas pela presença do prefixo personalizado.
❌Exemplo Incorreto:
// Evite!
@Directive({
standalone: true,
selector: '[validate]', // Sem prefixo personalizado
})
export class ValidateDirective {}
✅Exemplo Correto:
@Directive({
standalone: true,
selector: '[tohValidate]', // Prefixo "toh" para "Tour of Heroes"
})
export class ValidateDirective {}
No exemplo correto, a diretiva recebe o prefixo toh
, indicando que pertence à aplicação “Tour of Heroes”. Isso evita conflitos de nomes e facilita a identificação da diretiva no código.
Ao adotar essas práticas, você estará nomeando suas diretivas com precisão e clareza, como um mestre artesão que escolhe seus pincéis com cuidado. Cada diretiva se tornará um instrumento preciso para moldar o comportamento e a aparência dos elementos da sua aplicação Angular.
Nomes de Pipes: Refinando a Apresentação
Em Angular, os pipes são como filtros que transformam dados para exibição, adicionando um toque final à apresentação. Assim como um mestre artesão utiliza ferramentas para polir e aperfeiçoar sua obra, a nomenclatura dos pipes deve refletir sua função de forma clara e consistente.
✅Faça: Use nomes consistentes para todos os pipes, nomeando-os de acordo com sua funcionalidade. O nome da classe do pipe deve usar PascalCase
(maiúsculas no início de cada palavra), e a string name
correspondente deve usar camelCase
(primeira letra minúscula, demais maiúsculas no início de cada palavra). A string name
não pode usar hífens (“dash-case” ou “kebab-case”).
❓Por quê? Fornece uma maneira consistente de identificar e referenciar pipes rapidamente, além de garantir a compatibilidade com a sintaxe do Angular.
Exemplos:
✅Bom Exemplo
// Arquivo: app/shared/ellipsis.pipe.ts
@Pipe({
standalone: true,
name: 'ellipsis',
})
export class EllipsisPipe implements PipeTransform {
transform(value: string, limit: number): string {
return value.length > limit ? value.substring(0, limit) + '...' : value;
}
}
// Arquivo: app/shared/init-caps.pipe.ts
@Pipe({
standalone: true,
name: 'initCaps',
})
export class InitCapsPipe implements PipeTransform {
transform(value: string): string {
return value.replace(/\b\w/g, first => first.toUpperCase());
}
}
❌Mau Exemplo
/* Evite */
@Pipe({
standalone: true,
name: 'ellipsis-pipe',
})
export class EllipsisPipe implements PipeTransform {
transform(value: string, limit: number): string {
return value.length > limit ? value.substring(0, limit) + '...' : value;
}
}
/* Evite */
@Pipe({
standalone: true,
name: 'init-caps-pipe',
})
export class InitCapsPipe implements PipeTransform {
transform(value: string): string {
return value.replace(/\b\w/g, first => first.toUpperCase());
}
}
No bom exemplo, os pipes EllipsisPipe
e InitCapsPipe
seguem a convenção de usar UpperCamelCase para o nome da classe e lowerCamelCase para a string name
, fornecendo uma maneira clara e consistente de identificar e referenciar os pipes. No exemplo ruim, o uso de hífens na string name
não segue a convenção, tornando os nomes inconsistentes e menos legíveis.
Nomes de Arquivos de Teste Unitário: A Busca pela Qualidade
Na busca pela qualidade do código Angular, os testes unitários são como inspetores rigorosos, examinando cada componente em busca de falhas. Assim como um mestre artesão testa suas criações para garantir sua durabilidade, os testes unitários garantem que cada parte da aplicação funcione como esperado.
✅Faça: Nomeie os arquivos de especificação de teste da mesma forma que o componente que eles testam.
✅Faça: Nomeie os arquivos de especificação de teste com o sufixo .spec
.
❓Por quê?
- Identificação Rápida: Fornece uma maneira consistente de identificar rapidamente os testes relacionados a cada componente.
- Padronização para Test Runners: Fornece um padrão consistente para que o Karma ou outros executores de teste encontrem e executem os testes automaticamente.
Exemplos:
Componentes
// Arquivo: app/heroes/heroes.component.spec.ts
describe('HeroesComponent', () => {
// especificações do teste
});
// Arquivo: app/heroes/hero-list.component.spec.ts
describe('HeroListComponent', () => {
// especificações do teste
});
// Arquivo: app/heroes/hero-detail.component.spec.ts
describe('HeroDetailComponent', () => {
// especificações do teste
});
Serviços
// Arquivo: app/shared/logger.service.spec.ts
describe('LoggerService', () => {
// especificações do teste
});
// Arquivo: app/heroes/hero.service.spec.ts
describe('HeroService', () => {
// especificações do teste
});
// Arquivo: app/shared/filter-text.service.spec.ts
describe('FilterTextService', () => {
// especificações do teste
});
Pipes
// Arquivo: app/shared/ellipsis.pipe.spec.ts
describe('EllipsisPipe', () => {
// especificações do teste
});
// Arquivo: app/shared/init-caps.pipe.spec.ts
describe('InitCapsPipe', () => {
// especificações do teste
});
Os nomes dos arquivos de teste seguem a convenção de usar o mesmo nome do componente, serviço ou pipe que estão testando, com o sufixo .spec
. Isso fornece uma maneira clara e consistente de identificar e localizar rapidamente os testes, além de facilitar a correspondência de padrões para runners de teste como o karma.
Ao adotar essa convenção, você estará organizando seus testes unitários de forma clara e consistente. Cada arquivo de teste se tornará um inspetor dedicado, garantindo a qualidade e a confiabilidade de cada componente da sua aplicação Angular.
Estrutura da Aplicação e NgModules: O Mapa da Jornada Angular
Na jornada de desenvolvimento Angular, a estrutura da aplicação é como um mapa que guia o explorador por diferentes regiões do código. É importante ter uma visão de curto prazo para a implementação e uma visão de longo prazo para o crescimento da aplicação.
Diretrizes Gerais de Estrutura: O Caminho da Organização
✅Faça: Comece pequeno, mas mantenha em mente onde a aplicação se direciona no futuro.
✅Faça: Tenha uma visão de curto prazo para a implementação e uma visão de longo prazo para o crescimento da aplicação.
✅Faça: Coloque todo o código da aplicação em uma pasta chamada src
.
💡Considere: Criar uma pasta para um componente quando ele tiver vários arquivos acompanhantes (.ts
, .html
, .css
e .spec
).
❓Por quê?
- Manutenibilidade: Ajuda a manter a estrutura da aplicação pequena e fácil de manter nos estágios iniciais, enquanto permite sua evolução à medida que a aplicação cresce.
- Organização: Componentes geralmente têm quatro arquivos e podem rapidamente desordenar uma pasta se não forem organizados em sua própria pasta.
Exemplo de Estrutura de Pastas e Arquivos: A Cartografia da Aplicação
🌌 projeto-root
├── 🚀 src
│ ├── 🌠 app
│ │ ├── 🌌 core
│ │ │ ├── exception.service.ts
│ │ │ ├── exception.service.spec.ts
│ │ │ ├── starship-profile.service.ts
│ │ │ └── starship-profile.service.spec.ts
│ │ ├── 🛸 starships
│ │ │ ├── starship
│ │ │ │ ├── starship.component.ts
│ │ │ │ ├── starship.component.html
│ │ │ │ ├── starship.component.css
│ │ │ │ └── starship.component.spec.ts
│ │ │ ├── starship-list
│ │ │ │ ├── starship-list.component.ts
│ │ │ │ ├── starship-list.component.html
│ │ │ │ ├── starship-list.component.css
│ │ │ │ └── starship-list.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── starship-button.component.ts
│ │ │ │ ├── starship-button.component.html
│ │ │ │ ├── starship-button.component.css
│ │ │ │ └── starship-button.component.spec.ts
│ │ │ │ ├── starship.model.ts
│ │ │ │ └── starship.service.ts
│ │ │ │ └── starship.service.spec.ts
│ │ │ ├── starships.component.ts
│ │ │ ├── starships.component.html
│ │ │ ├── starships.component.css
│ │ │ └── starships.component.spec.ts
│ │ │ └── starships.routes.ts
│ │ ├── shared
│ │ │ ├── init-caps.pipe.ts
│ │ │ ├── init-caps.pipe.spec.ts
│ │ │ ├── filter-text.component.ts
│ │ │ ├── filter-text.component.spec.ts
│ │ │ ├── filter-text.service.ts
│ │ │ └── filter-text.service.spec.ts
│ │ ├── 🦹♂️ villains
│ │ │ ├── villain
│ │ │ │ ├── villain.component.ts
│ │ │ │ ├── villain.component.html
│ │ │ │ ├── villain.component.css
│ │ │ │ └── villain.component.spec.ts
│ │ │ ├── villain-list
│ │ │ │ ├── villain-list.component.ts
│ │ │ │ ├── villain-list.component.html
│ │ │ │ ├── villain-list.component.css
│ │ │ │ └── villain-list.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── villain-button.component.ts
│ │ │ │ ├── villain-button.component.html
│ │ │ │ ├── villain-button.component.css
│ │ │ │ └── villain-button.component.spec.ts
│ │ │ ├── villains.component.ts
│ │ │ ├── villains.component.html
│ │ │ ├── villains.component.css
│ │ │ ├── villains.component.spec.ts
│ │ │ ├── villains.module.ts
│ │ │ └── villains-routing.module.ts
│ │ ├── app.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.component.spec.ts
│ │ └── app.routes.ts
│ ├── main.ts
│ ├── index.html
│ └── ...
└── node_modules/
└── ...
Observação: Embora a organização de componentes em pastas dedicadas seja amplamente preferida, outra opção para aplicações pequenas é manter os componentes em uma estrutura plana (não em uma pasta dedicada). Isso adiciona até quatro arquivos à pasta existente, mas também reduz a profundidade das pastas. Seja qual for a sua escolha, seja consistente.
Em nossa jornada, exploraremos cada elemento dessa estrutura em detalhes, desvendando os segredos da organização modular em Angular e construindo aplicações escaláveis e de fácil manutenção.
Estrutura por Funcionalidade: A Arte da Organização Angular
Na tapeçaria complexa de uma aplicação Angular, a organização por funcionalidades é como separar os fios por cores, criando padrões e facilitando a compreensão. Ao agrupar elementos relacionados em pastas específicas, você simplifica a navegação e a manutenção do código.
Pastas por Funcionalidade: A Harmonia da Estrutura
✅Faça: Crie pastas com nomes que representem a área funcional que elas contêm.
❓Por quê?
- Legibilidade: Um desenvolvedor pode localizar o código e identificar o que cada arquivo representa rapidamente.
- Organização: A estrutura se torna mais plana e evita nomes repetitivos ou redundantes, tornando o projeto mais limpo e fácil de navegar.
- Escalabilidade: Quando há muitos arquivos, a localização se torna mais fácil com uma estrutura de pastas consistente.
Para mais informações, consulte o exemplo de estrutura de pastas e arquivos apresentado anteriormente.
O Módulo Raiz da Aplicação: O Alicerce da Arquitetura Angular
IMPORTANTE: As recomendações a seguir se aplicam a aplicações baseadas em NgModule
. Novas aplicações devem usar componentes, diretivas e pipes autônomos (standalone) em vez de NgModules.
Diretriz de Estilo para o Módulo Raiz:
✅Faça: Crie um NgModule
na pasta raiz da aplicação, por exemplo, em /src/app
se você estiver criando uma aplicação baseada em NgModule
.
❓Por quê? Toda aplicação baseada em NgModule
requer pelo menos um módulo raiz para iniciar a aplicação.
💡Considere: Nomear o módulo raiz como app.module.ts
.
❓Por quê? Facilita a localização e a identificação do módulo raiz.
Módulos de Funcionalidades: As Regiões da Aplicação Angular
Em uma aplicação Angular em expansão, os módulos de funcionalidades (feature modules) são como as diferentes regiões de um reino, cada uma com suas próprias características e responsabilidades. Assim como um mestre artesão divide seu projeto em partes menores para facilitar a execução, os módulos de funcionalidades permitem organizar o código de forma modular e escalável.
Diretrizes de Estilo para Módulos de Funcionalidades:
✅Faça: Crie um NgModule
para cada funcionalidade distinta da aplicação; por exemplo, uma funcionalidade “Heróis”.
✅Faça: Coloque o módulo de funcionalidade na pasta com o mesmo nome da área da funcionalidade; por exemplo, em app/herois
.
✅Faça: Nomeie o arquivo do módulo de funcionalidade refletindo o nome da área da funcionalidade e da pasta; por exemplo, app/herois/herois.module.ts
.
✅Faça: Nomeie o símbolo do módulo de funcionalidade refletindo o nome da área da funcionalidade, pasta e arquivo; por exemplo, app/herois/herois.module.ts
define HeroisModule
.
❓Por quê?
- Encapsulamento: Um módulo de funcionalidade pode expor ou ocultar sua implementação de outros módulos, como um reino que controla suas fronteiras.
- Organização: Um módulo de funcionalidade identifica conjuntos distintos de componentes relacionados que compõem a área da funcionalidade.
- Roteamento: Um módulo de funcionalidade pode ser facilmente roteado tanto de forma eager (carregamento imediato) quanto lazy (carregamento sob demanda).
- Limites Claros: Um módulo de funcionalidade define limites claros entre funcionalidades específicas e outras áreas da aplicação.
- Responsabilidades: Um módulo de funcionalidade ajuda a esclarecer e facilitar a atribuição de responsabilidades de desenvolvimento a diferentes equipes.
- Testes Isolados: Um módulo de funcionalidade pode ser facilmente isolado para testes, permitindo testar cada parte da aplicação de forma independente.
Estrutura de Pastas por Funcionalidade
🌌 projeto-root
├── 🚀 src
│ ├── 🌠 app
│ │ ├── 🌌 core
│ │ │ ├── exception.service.ts
│ │ │ ├── exception.service.spec.ts
│ │ │ ├── starship-profile.service.ts
│ │ │ └── starship-profile.service.spec.ts
│ │ ├── 🛸 starships
│ │ │ ├── starship
│ │ │ │ ├── starship.component.ts
│ │ │ │ ├── starship.component.html
│ │ │ │ ├── starship.component.css
│ │ │ │ └── starship.component.spec.ts
│ │ │ ├── starship-list
│ │ │ │ ├── starship-list.component.ts
│ │ │ │ ├── starship-list.component.html
│ │ │ │ ├── starship-list.component.css
│ │ │ │ └── starship-list.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── starship-button.component.ts
│ │ │ │ ├── starship-button.component.html
│ │ │ │ ├── starship-button.component.css
│ │ │ │ └── starship-button.component.spec.ts
│ │ │ │ ├── starship.model.ts
│ │ │ │ └── starship.service.ts
│ │ │ │ └── starship.service.spec.ts
│ │ │ ├── starships.component.ts
│ │ │ ├── starships.component.html
│ │ │ ├── starships.component.css
│ │ │ └── starships.component.spec.ts
│ │ │ └── starships.routes.ts
│ │ ├── shared
│ │ │ ├── init-caps.pipe.ts
│ │ │ ├── init-caps.pipe.spec.ts
│ │ │ ├── filter-text.component.ts
│ │ │ ├── filter-text.component.spec.ts
│ │ │ ├── filter-text.service.ts
│ │ │ └── filter-text.service.spec.ts
│ │ ├── 🦹♂️ villains
│ │ │ ├── villain
│ │ │ │ ├── villain.component.ts
│ │ │ │ ├── villain.component.html
│ │ │ │ ├── villain.component.css
│ │ │ │ └── villain.component.spec.ts
│ │ │ ├── villain-list
│ │ │ │ ├── villain-list.component.ts
│ │ │ │ ├── villain-list.component.html
│ │ │ │ ├── villain-list.component.css
│ │ │ │ └── villain-list.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── villain-button.component.ts
│ │ │ │ ├── villain-button.component.html
│ │ │ │ ├── villain-button.component.css
│ │ │ │ └── villain-button.component.spec.ts
│ │ │ ├── villains.component.ts
│ │ │ ├── villains.component.html
│ │ │ ├── villains.component.css
│ │ │ ├── villains.component.spec.ts
│ │ │ ├── villains.module.ts
│ │ │ └── villains-routing.module.ts
│ │ ├── app.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.component.spec.ts
│ │ └── app.routes.ts
│ ├── main.ts
│ ├── index.html
│ └── ...
└── node_modules/
└── ...
Ao adotar essas diretrizes, você estará organizando sua aplicação Angular como um reino bem estruturado, com cada módulo de funcionalidade representando uma região distinta. Essa abordagem modular facilita a manutenção, o reuso de código e a escalabilidade do projeto.
clonador.sitepressbr.com/v2umkz9/angular-6-modal-popup-example.html
Módulo de Funcionalidades Compartilhadas: A Caixa de Ferramentas do Artesão Angular
No universo Angular, o módulo de funcionalidades compartilhadas (SharedModule
) é como uma caixa de ferramentas que contém componentes, diretivas e pipes reutilizáveis em toda a aplicação. Assim como um mestre artesão guarda suas ferramentas mais versáteis em um local de fácil acesso, o SharedModule
centraliza elementos comuns para facilitar a manutenção e o desenvolvimento.
Diretrizes de Estilo para o Módulo Compartilhado:
Faça: Crie um módulo de funcionalidade chamado SharedModule
em uma pasta compartilhado
; por exemplo, app/compartilhado/compartilhado.module.ts
define SharedModule
.
Faça: Declare componentes, diretivas e pipes no SharedModule
quando esses itens forem reutilizados e referenciados por componentes declarados em outros módulos de funcionalidades.
Considere: Usar o nome SharedModule
quando o conteúdo do módulo for referenciado em toda a aplicação.
Considere: Não fornecer serviços em módulos compartilhados. Serviços geralmente são singletons que são fornecidos uma vez para toda a aplicação ou em um módulo de funcionalidade específico. No entanto, há exceções. Por exemplo, no código de exemplo a seguir, o SharedModule
fornece o FiltroTextoService
. Isso é aceitável aqui porque o serviço é stateless, ou seja, os consumidores do serviço não são afetados por novas instâncias.
Faça: Importe todos os módulos necessários para os ativos no SharedModule
; por exemplo, CommonModule
e FormsModule
.
Por quê? O SharedModule
conterá componentes, diretivas e pipes que podem precisar de recursos de outros módulos comuns, como o ngFor
do CommonModule
.
Faça: Declare todos os componentes, diretivas e pipes no SharedModule
.
Faça: Exporte todos os símbolos do SharedModule
que outros módulos de funcionalidades precisam usar.
Por quê? O SharedModule
existe para tornar componentes, diretivas e pipes comumente usados disponíveis para uso nos templates de componentes em muitos outros módulos.
Evite: Especificar provedores de singleton em todo o aplicativo em um SharedModule
. Singletons intencionais são aceitáveis, mas tome cuidado.
Por quê? Um módulo de funcionalidade com carregamento sob demanda (lazy loading) que importa o SharedModule
criará sua própria cópia do serviço, o que pode levar a resultados indesejáveis. Cada módulo teria sua própria instância de serviços que deveriam ser singletons.
Estrutura de Pastas e Arquivos do Módulo Compartilhado
🌌 projeto-root
├── 🚀 src
│ ├── 🌠 app
│ │ ├── shared
│ │ │ ├── shared.module.ts
│ │ │ ├── init-caps.pipe.ts
│ │ │ ├── init-caps.pipe.spec.ts
│ │ │ ├── filter-text
│ │ │ │ ├── filter-text.component.ts
│ │ │ │ ├── filter-text.component.spec.ts
│ │ │ │ ├── filter-text.service.ts
│ │ │ │ └── filter-text.service.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.component.spec.ts
│ │ ├── app.module.ts
│ │ ├── app-routing.module.ts
│ ├── main.ts
│ ├── index.html
│ └── ...
└── node_modules/
└── ...
Exemplo de shared.module.ts
// Arquivo: src/app/shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FilterTextComponent } from './filter-text/filter-text.component';
import { FilterTextService } from './filter-text/filter-text.service';
import { InitCapsPipe } from './init-caps.pipe';
@NgModule({
imports: [
CommonModule,
FormsModule,
],
declarations: [
FilterTextComponent,
InitCapsPipe,
],
providers: [FilterTextService],
exports: [
CommonModule,
FormsModule,
FilterTextComponent,
InitCapsPipe,
],
})
export class SharedModule {}
Este exemplo demonstra um SharedModule
que contém componentes, pipes e um serviço stateless, todos exportados para serem usados em outros módulos da aplicação.
Ao seguir essas diretrizes, você estará criando um SharedModule
eficiente e bem organizado, facilitando o reuso de código e a manutenção da sua aplicação Angular.
Pastas para Carregamento Sob Demanda (Lazy Loading): A Eficiência na Arquitetura Angular
Na vastidão de uma aplicação Angular, nem todas as funcionalidades precisam ser carregadas de uma só vez. Assim como um mestre artesão prepara apenas as ferramentas necessárias para cada etapa do projeto, o carregamento sob demanda (lazy loading) permite otimizar o desempenho da aplicação, carregando apenas o que é necessário em cada momento.
Pastas para Lazy Loading: Organização para a Eficiência
✅Diretriz de Estilo:
- Uma funcionalidade ou fluxo de trabalho distinto da aplicação pode ser carregado sob demanda, em vez de ser carregado no início da aplicação.
Faça: Coloque o conteúdo de funcionalidades carregadas sob demanda em uma pasta para lazy loading. Uma pasta típica para lazy loading contém um componente de roteamento, seus componentes filhos e seus ativos relacionados.
❓Por quê? A pasta facilita a identificação e o isolamento do conteúdo da funcionalidade.
Benefícios:
- Melhora do tempo de carregamento inicial: A aplicação carrega mais rapidamente, pois apenas as funcionalidades essenciais são carregadas no início.
- Organização por funcionalidade: O código é organizado por funcionalidade, facilitando a manutenção e a compreensão da estrutura do projeto.
- Isolamento de conteúdo: O conteúdo das funcionalidades carregadas sob demanda é isolado em uma pasta específica, tornando o código mais modular e fácil de gerenciar.
Estrutura de Pastas para Funcionalidades Carregadas de Forma Preguiçosa
🌌 projeto-root
├── 🚀 src
│ ├── 🌠 app
│ │ ├── 🛸 starships
│ │ │ ├── starship
│ │ │ ├── starship-list
│ │ │ ├── shared
│ │ │ ├── starships.component.ts
│ │ │ ├── starships.module.ts
│ │ │ └── starships-routing.module.ts
│ │ ├── 🦹♂️ villains
│ │ │ ├── villain
│ │ │ ├── villain-list
│ │ │ ├── shared
│ │ │ ├── villains.component.ts
│ │ │ ├── villains.module.ts
│ │ │ └── villains-routing.module.ts
│ │ ├── 🌟 missions (lazy-loaded)
│ │ │ ├── mission
│ │ │ │ ├── mission.component.ts
│ │ │ │ ├── mission.component.html
│ │ │ │ ├── mission.component.css
│ │ │ │ └── mission.component.spec.ts
│ │ │ ├── mission-list
│ │ │ │ ├── mission-list.component.ts
│ │ │ │ ├── mission-list.component.html
│ │ │ │ ├── mission-list.component.css
│ │ │ │ └── mission-list.component.spec.ts
│ │ │ ├── shared
│ │ │ │ ├── mission-button.component.ts
│ │ │ │ ├── mission-button.component.html
│ │ │ │ ├── mission-button.component.css
│ │ │ │ └── mission-button.component.spec.ts
│ │ │ ├── missions.component.ts
│ │ │ ├── missions.module.ts
│ │ │ └── missions-routing.module.ts
│ │ ├── core
│ │ ├── shared
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── app-routing.module.ts
│ ├── main.ts
│ ├── index.html
│ └── ...
└── node_modules/
└── ...
Organizar o conteúdo das funcionalidades carregadas de forma preguiçosa em pastas dedicadas facilita a identificação e isolamento do conteúdo da funcionalidade, além de melhorar a manutenção e escalabilidade da aplicação.
Componentes: Os Blocos de Construção da Aplicação Angular
Em Angular, os componentes são os blocos de construção fundamentais da interface do usuário. Assim como um mestre artesão utiliza diferentes peças para montar sua obra, os componentes permitem criar interfaces complexas e reutilizáveis, tornando o desenvolvimento mais eficiente e organizado.
Componentes como Elementos: A Essência da Interface
💡Considere: Dar aos componentes um seletor de elemento, em vez de seletores de atributo ou classe.
❓Por quê?
- HTML Semântico: Componentes possuem templates contendo HTML e sintaxe de template Angular opcional. Eles exibem conteúdo e são posicionados na página como elementos HTML nativos e componentes web.
- Identificação Facilitada: É mais fácil reconhecer que um símbolo é um componente olhando para o HTML do template quando ele possui um seletor de elemento.
Observação: Há casos em que você pode dar a um componente um seletor de atributo, como quando deseja aumentar um elemento integrado. Por exemplo, o Material Design usa essa técnica com <button mat-button>
. No entanto, essa técnica não seria usada em um elemento personalizado.
❌Exemplo Incorreto:
// Evite!
@Component({
standalone: true,
selector: '[tohHeroButton]', // Seletor de atributo (não recomendado)
templateUrl: './hero-button.component.html',
})
export class HeroButtonComponent {}
✅Exemplo Correto:
@Component({
standalone: true,
selector: 'toh-hero-button', // Seletor de elemento (recomendado)
templateUrl: './hero-button.component.html',
})
export class HeroButtonComponent {}
No exemplo correto, o componente HeroButtonComponent
possui um seletor de elemento (toh-hero-button
), seguindo a prática recomendada para componentes que representam elementos visuais na interface do usuário.
Ao adotar essa convenção, você estará utilizando os componentes da forma mais intuitiva e semântica, tornando seu código Angular mais legível e fácil de manter.
Extração de Templates e Estilos para Arquivos Separados: A Clareza na Apresentação
Assim como um mestre artesão separa os moldes e as ferramentas de acabamento de suas criações, a prática de extrair templates e estilos para arquivos separados em componentes Angular melhora a organização e a legibilidade do código.
Diretrizes de Estilo para Templates e Estilos:
✅Faça: Extraia templates e estilos para um arquivo separado quando tiverem mais de 3 linhas.
✅Faça: Nomeie o arquivo de template como [nome-do-componente].component.html
, onde [nome-do-componente]
é o nome do componente.
Faça: Nomeie o arquivo de estilo como [nome-do-componente].component.css
, onde [nome-do-componente]
é o nome do componente.
Faça: Especifique URLs relativos ao componente, prefixados com ./
.
Por quê?
- Legibilidade e Manutenibilidade: Templates e estilos inline extensos obscurecem o propósito e a implementação do componente, dificultando a leitura e a manutenção.
- Ferramentas de Desenvolvimento: Em muitos editores, dicas de sintaxe e snippets de código não estão disponíveis ao desenvolver templates e estilos inline. O Angular Language Service (em desenvolvimento) promete superar essa deficiência para templates HTML em editores que o suportam, mas não ajudará com estilos CSS.
- Portabilidade: Uma URL relativa ao componente não requer alterações quando você move os arquivos do componente, desde que os arquivos permaneçam juntos.
- Padrão para URLs Relativas: O prefixo
./
é a sintaxe padrão para URLs relativas; não dependa da capacidade atual do Angular de funcionar sem esse prefixo.
❌Exemplo Incorreto: Template e Estilo Inline
/* Evite */
@Component({
standalone: true,
selector: 'toh-heroes',
template: `
<div>
<h2>My Heroes</h2>
<ul class="heroes">
@for (hero of heroes | async; track hero) {
<li (click)="selectedHero=hero">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
}
</ul>
@if (selectedHero) {
<div>
<h2>{{selectedHero.name | uppercase}} is my hero</h2>
</div>
}
</div>
`,
styles: [
`
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
`,
],
imports: [NgFor, NgIf, AsyncPipe, UpperCasePipe],
})
export class HeroesComponent {
heroes: Observable<Hero[]>;
selectedHero!: Hero;
constructor(private heroService: HeroService) {
this.heroes = this.heroService.getHeroes();
}
}
✅Exemplo Correto: Template e Estilo em Arquivos Separados
Arquivo: app/heroes/heroes.component.ts
@Component({
standalone: true,
selector: 'toh-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css'],
imports: [NgFor, NgIf, AsyncPipe, UpperCasePipe],
})
export class HeroesComponent {
heroes: Observable<Hero[]>;
selectedHero!: Hero;
constructor(private heroService: HeroService) {
this.heroes = this.heroService.getHeroes();
}
}
Arquivo: app/heroes/heroes.component.html
<div>
<h2>My Heroes</h2>
<ul class="heroes">
@for (hero of heroes | async; track hero) {
<li>
<button type="button" (click)="selectedHero=hero">
<span class="badge">{{ hero.id }}</span>
<span class="name">{{ hero.name }}</span>
</button>
</li>
}
</ul>
@if (selectedHero) {
<div>
<h2>{{ selectedHero.name | uppercase }} is my hero</h2>
</div>
}
</div>
Arquivo: app/heroes/heroes.component.css
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: 0;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #405061;
line-height: 1em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
align-self: center;
}
Por que o exemplo é melhor?
No bom exemplo, o template e os estilos foram extraídos para arquivos próprios (heroes.component.html
e heroes.component.css
), melhorando a legibilidade e manutenção do código. As URLs relativas ao componente (./
) garantem que não haja necessidade de alterações ao mover os arquivos do componente.
Ao adotar essa prática, você estará separando a estrutura (template) da apresentação (estilo) e da lógica (componente TypeScript), tornando seu código Angular mais organizado, legível e fácil de manter.
Decorando Propriedades de Entrada e Saída: A Comunicação dos Componentes
Na rede de comunicação entre os componentes Angular, as propriedades de entrada (@Input
) e saída (@Output
) são como fios que transmitem informações. Assim como um mestre artesão conecta diferentes partes de sua obra, os decoradores @Input
e @Output
permitem que os componentes se comuniquem e interajam entre si.
Diretrizes de Estilo para Decorar Entradas e Saídas:
✅Faça: Use os decoradores de classe @Input()
e @Output()
em vez das propriedades inputs
e outputs
dos metadados @Directive
e @Component
.
💡Considere: Colocar @Input()
ou @Output()
na mesma linha da propriedade que eles decoram.
❓Por quê?
- Identificação Clara: Facilita a identificação de quais propriedades em uma classe são entradas ou saídas.
- Manutenção Simplificada: Se você precisar renomear a propriedade ou o nome do evento associado a
@Input()
ou@Output()
, pode modificá-lo em um único lugar. - Declaração de Metadados Mais Legível: A declaração de metadados anexada à diretiva ou componente se torna mais curta e legível.
- Código Mais Conciso: Colocar o decorador na mesma linha geralmente torna o código mais curto e ainda identifica facilmente a propriedade como entrada ou saída. Coloque-o na linha acima quando isso tornar o código mais legível.
Exemplos de Decoração de Propriedades de Entrada e Saída
❌Mau Exemplo
/* Evite */
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">{{label}}</button>`,
inputs: ['label'],
outputs: ['heroChange'],
})
export class HeroButtonComponent {
heroChange = new EventEmitter<any>();
label: string = '';
}
✅Bom Exemplo
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">{{label}}</button>`,
})
export class HeroButtonComponent {
@Output() heroChange = new EventEmitter<any>();
@Input() label = '';
}
No exemplo correto, as propriedades heroChange
e label
são decoradas com @Output
e @Input
, respectivamente, tornando a intenção do código mais clara e facilitando a identificação das propriedades de entrada e saída.
Evitando Aliasing de Entradas e Saídas: A Clareza na Comunicação
Na troca de informações entre componentes Angular, a clareza é fundamental. O aliasing, ou seja, o uso de nomes diferentes para a mesma propriedade no template e no componente, pode gerar confusão e dificultar a compreensão do código.
Diretriz de Estilo para Aliasing:
❌Evite: Aliasing de entradas e saídas, exceto quando serve a um propósito importante.
❓Por quê?
- Confusão Inerente: Ter dois nomes para a mesma propriedade (um privado e outro público) é confuso por natureza.
- 💡Exceção: Você deve usar um alias quando o nome da diretiva também é uma propriedade de entrada e o nome da diretiva não descreve a propriedade.
❌Exemplo Incorreto: Aliasing Desnecessário
/* Evite aliasing desnecessário */
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">{{label}}</button>`,
})
export class HeroButtonComponent {
// Aliases desnecessários
@Output('heroChangeEvent') heroChange = new EventEmitter<any>();
@Input('labelAttribute') label!: string;
}
❌HTML do Template (Mau Exemplo)
<!-- Evite -->
<toh-hero-button labelAttribute="OK" (heroChangeEvent)="doSomething()">
</toh-hero-button>
✅Exemplo Correto: Sem Aliasing
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">{{label}}</button>`,
})
export class HeroButtonComponent {
// Sem aliases
@Output() heroChange = new EventEmitter<any>();
@Input() label = '';
}
✅HTML do Template (Bom Exemplo)
<toh-hero-button label="OK" (heroChange)="doSomething()">
</toh-hero-button>
No exemplo correto, as propriedades heroChange
e label
não possuem aliases, tornando o código mais claro e fácil de entender.
Evitar aliases de inputs e outputs, a menos que seja absolutamente necessário, melhora a clareza e a manutenção do código. Quando os aliases são evitados, os desenvolvedores podem entender mais facilmente quais propriedades são públicas e como elas são usadas.
Delegando Lógica Complexa a Serviços: A Simplicidade e o Foco dos Componentes
Na orquestra Angular, os componentes são os maestros que coordenam a apresentação visual, enquanto os serviços são os músicos que executam as melodias complexas da lógica de negócios. Assim como um maestro confia nos músicos para tocar suas partes, os componentes devem delegar a lógica complexa aos serviços, mantendo-se simples e focados em seu propósito principal.
Diretrizes de Estilo para Delegação de Lógica:
✅Faça: Limite a lógica em um componente apenas ao que é necessário para a visualização. Toda a lógica restante deve ser delegada aos serviços.
✅Faça: Mova a lógica reutilizável para serviços e mantenha os componentes simples e focados em seu propósito.
❓Por quê?
- Reutilização: A lógica pode ser reutilizada por vários componentes quando colocada em um serviço e exposta como uma função.
- Testes Unitários: A lógica em um serviço pode ser facilmente isolada em um teste unitário, enquanto a lógica de chamada no componente pode ser facilmente simulada (mocked).
- Abstração: Remove dependências e oculta detalhes de implementação do componente, tornando-o mais fácil de entender e manter.
- Simplicidade: Mantém o componente enxuto, focado em sua função de apresentação visual.
❌Exemplo Incorreto: Lógica Complexa no Componente
/* Evite */
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { Hero } from '../shared/hero.model';
const heroesUrl = 'http://angular.io';
@Component({
standalone: true,
selector: 'toh-hero-list',
template: `...`,
})
export class HeroListComponent implements OnInit {
heroes: Hero[];
constructor(private http: HttpClient) {
this.heroes = [];
}
getHeroes() {
this.heroes = [];
this.http
.get(heroesUrl)
.pipe(
catchError(this.catchBadResponse),
finalize(() => this.hideSpinner()),
)
.subscribe((heroes: Hero[]) => (this.heroes = heroes));
}
ngOnInit() {
this.getHeroes();
}
private catchBadResponse(err: any, source: Observable<any>) {
// log and handle the exception
return new Observable();
}
private hideSpinner() {
// hide the spinner
}
}
✅Exemplo Correto: Delegação de Lógica para o Serviço
import { Component, OnInit } from '@angular/core';
import { Hero, HeroService } from '../shared';
@Component({
standalone: true,
selector: 'toh-hero-list',
template: `...`,
})
export class HeroListComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) {}
getHeroes() {
this.heroes = [];
this.heroService.getHeroes().subscribe((heroes) => (this.heroes = heroes));
}
ngOnInit() {
this.getHeroes();
}
}
No exemplo correto, o componente ListaHeroisComponent
delega a lógica de obtenção de dados para o serviço HeroiService
. O componente se concentra apenas em exibir os dados e responder a eventos do usuário, tornando-o mais simples e fácil de manter.
Ao delegar a lógica complexa para serviços, você estará criando uma arquitetura Angular mais modular, reutilizável e testável. Seus componentes se tornarão maestros elegantes, conduzindo a orquestra da sua aplicação com maestria e foco.
Não Use Prefixos em Propriedades de Saída: A Elegância na Notificação de Eventos
Na comunicação entre componentes Angular, os eventos são como sinais que um componente envia para notificar outros sobre mudanças em seu estado. Assim como um mestre artesão sinaliza a conclusão de uma etapa do projeto, os eventos em Angular permitem que os componentes interajam de forma dinâmica e responsiva.
Diretrizes de Estilo para Nomes de Eventos:
✅Faça: Nomeie eventos sem o prefixo on
.
✅Faça: Nomeie os métodos que lidam com eventos com o prefixo on
, seguido pelo nome do evento.
❓Por quê?
- Consistência com Eventos Nativos: Essa convenção é consistente com eventos nativos do JavaScript, como cliques de botão (
click
). - Sintaxe Alternativa: Angular permite a sintaxe alternativa
on-*
. Se o próprio evento fosse prefixado comon
, isso resultaria em uma expressão de ligaçãoon-onEvent
, o que não é desejável.
❌Exemplo Incorreto: Prefixo on
em Evento
/* Evite */
@Component({
standalone: true,
selector: 'toh-hero',
template: `...`,
})
export class HeroComponent {
@Output() onSavedTheDay = new EventEmitter<boolean>();
}
❌HTML do Template (Mau Exemplo)
<!-- Evite -->
<toh-hero (onSavedTheDay)="onSavedTheDay($event)"></toh-hero>
✅Exemplo Correto: Sem Prefixo em Evento
@Component({
standalone: true,
selector: 'toh-hero',
template: `...`,
})
export class HeroComponent {
@Output() savedTheDay = new EventEmitter<boolean>();
}
✅HTML do Template (Bom Exemplo)
<toh-hero (savedTheDay)="onSavedTheDay($event)"></toh-hero>
No exemplo correto, o evento é nomeado como salvouODia
, sem o prefixo on
. Isso torna o código mais limpo e consistente com as convenções de nomenclatura do Angular.
Ao seguir essa diretriz, você estará nomeando seus eventos de forma clara e elegante, facilitando a compreensão e a manutenção do seu código Angular.
Lógica de Apresentação na Classe do Componente: A Centralização da Inteligência Visual
Na criação de componentes Angular, a lógica responsável pela apresentação visual deve ser mantida na classe do componente, e não espalhada pelo template. Assim como um mestre artesão guarda seus segredos de design em sua mente, a classe do componente centraliza a inteligência visual, tornando o código mais organizado e testável.
Diretriz de Estilo para Lógica de Apresentação:
✅Faça: Coloque a lógica de apresentação na classe do componente, e não no template.
❓Por quê?
- Centralização: A lógica estará contida em um único lugar (a classe do componente), em vez de ser espalhada em dois.
- Testabilidade, Manutenibilidade e Reusabilidade: Manter a lógica de apresentação do componente na classe, em vez do template, melhora a testabilidade, a manutenibilidade e a reusabilidade.
❌Exemplo Incorreto: Lógica de Apresentação no Template
/* Evite */
@Component({
standalone: true,
selector: 'toh-hero-list',
template: `
<section>
Our list of heroes:
@for (hero of heroes; track hero) {
<toh-hero [hero]="hero"></toh-hero>
}
Total powers: {{totalPowers}}<br>
Average power: {{totalPowers / heroes.length}}
</section>
`,
imports: [NgFor, HeroComponent],
})
export class HeroListComponent {
heroes: Hero[] = [];
totalPowers: number = 0;
}
✅Exemplo Correto: Lógica de Apresentação na Classe do Componente
@Component({
standalone: true,
selector: 'toh-hero-list',
template: `
<section>
Our list of heroes:
@for (hero of heroes; track hero) {
<toh-hero [hero]="hero"></toh-hero>
}
Total powers: {{totalPowers}}<br>
Average power: {{avgPower}}
</section>
`,
imports: [NgFor, HeroComponent],
})
export class HeroListComponent {
heroes: Hero[] = [];
totalPowers = 0;
get avgPower() {
return this.totalPowers / this.heroes.length;
}
}
No exemplo correto, a lógica para calcular o poder médio (avgPower
) é movida para um método getter na classe do componente. Isso torna o template mais limpo e a lógica de apresentação mais fácil de testar e reutilizar.
Ao centralizar a lógica de apresentação na classe do componente, você estará construindo componentes Angular mais robustos, legíveis e manuteníveis.
Inicialização de Inputs: Garantindo a Segurança de Tipos
A opção --strictPropertyInitialization
do compilador TypeScript garante que as propriedades de uma classe sejam inicializadas durante a construção. Quando habilitada, essa opção faz com que o compilador TypeScript relate um erro se a classe não definir um valor para qualquer propriedade que não seja explicitamente marcada como opcional.
Por design, o Angular trata todas as propriedades @Input
como opcionais. Sempre que possível, você deve satisfazer a opção --strictPropertyInitialization
fornecendo um valor padrão.
✅Exemplo: Valor Padrão para Propriedade de Entrada
@Component({
standalone: true,
selector: 'toh-hero',
template: `...`,
})
export class HeroComponent {
@Input() id = 'default_id';
}
Se a propriedade for difícil de construir um valor padrão, use ?
para marcar explicitamente a propriedade como opcional.
✅Exemplo: Propriedade de Entrada Opcional
@Component({
standalone: true,
selector: 'toh-hero',
template: `...`,
})
export class HeroComponent {
@Input() id?: string;
process() {
if (this.id) {
// ...
}
}
}
Você pode querer ter um campo @Input
obrigatório, o que significa que todos os usuários do seu componente são obrigados a passar esse atributo. Nesses casos, use um valor padrão. Apenas suprimir o erro do TypeScript com !
é insuficiente e deve ser evitado, pois impedirá que o verificador de tipos garanta que o valor de entrada seja fornecido.
❌Exemplo Incorreto: Suprimir Erro com !
(Non-null Assertion Operator)
@Component({
standalone: true,
selector: 'toh-hero',
template: `...`,
})
export class HeroComponent {
// A exclamação suprime erros de que uma propriedade não está inicializada.
// Ignorar essa verificação pode impedir o verificador de tipos de encontrar possíveis problemas.
@Input() id!: string;
}
Embora o ponto de exclamação (!
) possa suprimir o erro de compilação, ele não garante que a propriedade seja inicializada. Isso pode levar a erros em tempo de execução e deve ser evitado, pois impede que o verificador de tipo TypeScript encontre problemas potenciais.
Ao seguir essas diretrizes, você estará escrevendo um código Angular mais seguro e confiável, garantindo que as propriedades de entrada sejam sempre inicializadas de forma adequada.
Diretivas: Os Pinceladas de Comportamento no Angular
Em Angular, as diretivas são como pinceladas que adicionam comportamento e estilo aos elementos do DOM. Assim como um mestre artesão usa diferentes pincéis para criar texturas e efeitos, as diretivas permitem que você modifique a aparência e o comportamento dos elementos da sua aplicação.
Usando Diretivas para Aprimorar Elementos:
✅Faça: Use diretivas de atributo quando você tiver lógica de apresentação sem um template.
❓Por quê?
- Sem Template: Diretivas de atributo não possuem um template associado, ao contrário dos componentes.
- Múltiplas Diretivas: Um elemento pode ter várias diretivas de atributo aplicadas, cada uma adicionando um comportamento específico.
✅Exemplo: Diretiva de Atributo tohHighlight
@Directive({
standalone: true,
selector: '[tohHighlight]',
})
export class HighlightDirective {
@HostListener('mouseover') onMouseEnter() {
// lógica para destacar o elemento
}
}
✅Arquivo: app/app.component.html
<div tohHighlight>Bombasta</div>
Neste exemplo, a diretiva de atributo tohHighlight
adiciona um comportamento de destaque ao elemento div
quando o mouse passa sobre ele. A lógica de apresentação (destaque) é implementada na diretiva, sem a necessidade de um template.
Ao usar diretivas de atributo, você estará adicionando pinceladas de comportamento aos seus elementos, tornando sua aplicação Angular mais interativa e dinâmica.
Decoradores @HostListener
e @HostBinding
vs. Metadados host
: A Escolha da Ferramenta Certa
No arsenal de diretivas Angular, os decoradores @HostListener
e @HostBinding
são ferramentas poderosas para interagir com o elemento hospedeiro. Assim como um mestre artesão escolhe a ferramenta certa para cada tarefa, a escolha entre esses decoradores e os metadados host
pode afetar a legibilidade e a manutenibilidade do seu código.
Recomendação:
💡Considere: Dar preferência aos decoradores @HostListener
e @HostBinding
em relação à propriedade host
dos decoradores @Directive
e @Component
.
✅Faça: Seja consistente em sua escolha.
❓Por quê?
- Modificação em um Único Lugar: A propriedade associada a
@HostBinding
ou o método associado a@HostListener
pode ser modificado em um único lugar – na classe da diretiva. Se você usar a propriedade de metadadoshost
, precisará modificar tanto a declaração da propriedade/método na classe da diretiva quanto os metadados no decorador associado à diretiva.
✅Exemplo: Decoradores @HostListener
e @HostBinding
Arquivo: app/shared/validator.directive.ts
import { Directive, HostBinding, HostListener } from '@angular/core';
@Directive({
standalone: true,
selector: '[tohValidator]',
})
export class ValidatorDirective {
@HostBinding('attr.role') role = 'button';
@HostListener('mouseenter') onMouseEnter() {
// lógica ao passar o mouse
}
}
Comparação com a Alternativa de Metadados host
❓Por quê?
- Os metadados
host
são apenas um termo para lembrar e não requerem importações extras do ES.
Exemplo: Metadados host
(Menos Preferível)❌
Arquivo: app/shared/validator2.directive.ts
import { Directive } from '@angular/core';
@Directive({
standalone: true,
selector: '[tohValidator2]',
host: {
'[attr.role]': 'role',
'(mouseenter)': 'onMouseEnter()',
},
})
export class Validator2Directive {
role = 'button';
onMouseEnter() {
// lógica ao passar o mouse
}
}
Embora os metadados host
exijam apenas um termo para lembrar e não precisem de importações ES extras, eles podem levar a uma duplicação de informações e dificultar a manutenção do código.
Ao adotar a prática recomendada de usar @HostListener
e @HostBinding
, você estará optando por uma abordagem mais clara e manutenível para interagir com o elemento hospedeiro em suas diretivas Angular.
Serviços: Os Singletons Compartilhados do Angular
No mundo Angular, os serviços são como os sábios anciões de uma comunidade, compartilhando seu conhecimento e habilidades com todos que precisam. Assim como um mestre artesão confia em seus conselheiros para obter informações e recursos valiosos, os serviços em Angular são singletons que fornecem dados e funcionalidades compartilhadas em toda a aplicação.
Serviços como Singletons:
✅Faça: Use serviços como singletons dentro do mesmo injetor. Use-os para compartilhar dados e funcionalidades.
❓Por quê?
- Compartilhamento de Métodos: Serviços são ideais para compartilhar métodos em uma área funcional ou em toda a aplicação.
- Compartilhamento de Dados: Serviços são ideais para compartilhar dados stateful (com estado) na memória.
Exemplo: heroi.service.ts
Arquivo: app/heroes/shared/hero.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './hero.model';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor(private http: HttpClient) {}
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>('api/heroes');
}
}
Neste exemplo, o serviço HeroiService
é um singleton fornecido em toda a aplicação (graças a providedIn: 'root'
). Ele encapsula a lógica para obter dados de heróis de uma API e pode ser injetado em qualquer componente que precise dessa funcionalidade.
Ao usar serviços como singletons, você estará promovendo o compartilhamento de código e dados em sua aplicação Angular, tornando-a mais eficiente, organizada e fácil de manter.
Fornecendo um Serviço: A Injeção de Sabedoria no Angular
Na sociedade Angular, os serviços são como mentores sábios, prontos para compartilhar seus conhecimentos com qualquer componente que precise. A injeção de dependência (DI) é o mecanismo que permite que os componentes solicitem e recebam esses serviços de forma eficiente e organizada.
Fornecendo um Serviço com o Injetor Raiz:
✅Faça: Forneça um serviço com o injetor raiz da aplicação no decorador @Injectable
do serviço.
❓Por quê?
- Hierarquia de Injetores: O injetor do Angular é hierárquico, com o injetor raiz no topo. Fornecer um serviço no injetor raiz garante que ele esteja disponível para toda a aplicação.
- Singleton Compartilhado: Quando você fornece o serviço ao injetor raiz, essa instância do serviço é compartilhada e disponível em todas as classes que precisam do serviço. Isso é ideal quando um serviço está compartilhando métodos ou estado.
- Otimização: Ao registrar um serviço no decorador
@Injectable
do serviço, ferramentas de otimização, como as usadas pelas compilações de produção do Angular CLI, podem realizar tree shaking e remover serviços que não são usados pelo seu aplicativo.
Observação: Fornecer um serviço no injetor raiz não é ideal quando dois componentes diferentes precisam de instâncias diferentes de um serviço. Nesse cenário, seria melhor fornecer o serviço no nível do componente que precisa da nova instância separada.
Exemplo: service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class Service {
// lógica do serviço
}
Neste exemplo, o serviço Service
é fornecido no injetor raiz da aplicação. Isso significa que uma única instância desse serviço será criada e compartilhada por todos os componentes que o injetarem.
Ao fornecer seus serviços no injetor raiz, você estará facilitando a injeção de dependência e promovendo a reutilização de código em sua aplicação Angular.
Decorador de Classe @Injectable()
: Simplificando a Injeção de Dependências em Serviços Angular
No mundo Angular, a injeção de dependências é como um sistema de encanamento que fornece os recursos necessários para cada parte da casa. O decorador de classe @Injectable()
é a ferramenta que marca um serviço como apto a receber essas dependências, facilitando a comunicação e a colaboração entre os diferentes elementos da aplicação.
Usando o Decorador @Injectable()
:
✅Faça: Use o decorador de classe @Injectable()
em vez do decorador de parâmetro @Inject
ao usar tipos como tokens para as dependências de um serviço.
❓Por quê?
- Resolução Automática de Dependências: O mecanismo de Injeção de Dependências (DI) do Angular resolve as dependências de um serviço com base nos tipos declarados dos parâmetros do construtor do serviço.
- Sintaxe Menos Verbosa: Quando um serviço aceita apenas dependências associadas a tokens de tipo, a sintaxe
@Injectable()
é muito menos verbosa em comparação com o uso de@Inject()
em cada parâmetro individual do construtor.
❌Exemplo Incorreto: Uso de @Inject
em Parâmetros do Construtor
/* Evite */
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';
export class HeroArena {
constructor(
@Inject(HeroService) private heroService: HeroService,
@Inject(HttpClient) private http: HttpClient,
) {}
}
✅Exemplo Correto: Uso de @Injectable()
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';
@Injectable()
export class HeroArena {
constructor(
private heroService: HeroService,
private http: HttpClient,
) {}
}
Por que o exemplo com @Injectable()
é melhor?
- No bom exemplo, o uso de
@Injectable()
torna o código mais conciso e legível, eliminando a necessidade de usar@Inject
em cada parâmetro do construtor. - O Angular resolve as dependências com base nos tipos declarados dos parâmetros do construtor, simplificando a injeção de dependências.
Usar o decorador de classe @Injectable()
em vez do decorador de parâmetro @Inject
para injeção de dependências torna o código mais limpo e menos verboso. Essa prática melhora a legibilidade e manutenção do código, aproveitando ao máximo o mecanismo de DI do Angular.
Serviços de Dados: A Ponte entre a Aplicação e o Mundo Exterior
No universo Angular, os serviços de dados são como mensageiros que viajam entre a aplicação e o mundo exterior, buscando e entregando informações valiosas. Assim como um mestre artesão envia seus aprendizes para coletar materiais e informações, os serviços de dados são responsáveis por lidar com a comunicação com servidores, armazenamento local e outras operações relacionadas a dados.
Comunicação com o Servidor Através de um Serviço:
✅Faça: Refatore a lógica para realizar operações de dados e interagir com dados para um serviço.
✅Faça: Torne os serviços de dados responsáveis por chamadas XHR, armazenamento local, armazenamento em memória ou qualquer outra operação de dados.
❓Por quê?
- Separação de Responsabilidades: A responsabilidade do componente é pela apresentação e coleta de informações para a visualização. Ele não deve se preocupar em como obter os dados, apenas em saber quem pedir. Separar os serviços de dados move a lógica de como obtê-los para o serviço de dados e permite que o componente seja mais simples e focado na visualização.
- Testabilidade: Isso facilita o teste (mock ou real) das chamadas de dados ao testar um componente que usa um serviço de dados.
- Encapsulamento de Detalhes: Os detalhes do gerenciamento de dados, como cabeçalhos, métodos HTTP, cache, tratamento de erros e lógica de repetição, são irrelevantes para componentes e outros consumidores de dados. Um serviço de dados encapsula esses detalhes, tornando mais fácil evoluí-los dentro do serviço sem afetar seus consumidores. Também é mais fácil testar os consumidores com implementações de serviço mock.
Um serviço de dados encapsula esses detalhes. É mais fácil evoluir esses detalhes dentro do serviço sem afetar seus consumidores. E é mais fácil testar os consumidores com implementações de serviço mock.
Exemplo de Serviço de Dados (Data Services)
Arquivo: app/heroes/shared/hero.service.ts
Ao delegar a responsabilidade pela comunicação com o servidor e outras operações de dados para serviços dedicados, você estará criando uma arquitetura Angular mais modular, testável e fácil de manter. Seus componentes se concentrarão em sua função principal, que é a apresentação visual, enquanto os serviços de dados cuidarão da complexidade da interação com o mundo exterior.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './hero.model';
@Injectable({
providedIn: 'root',
})
export class HeroService {
private heroesUrl = 'api/heroes';
constructor(private http: HttpClient) {}
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl);
}
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url);
}
addHero(hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero);
}
deleteHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url);
}
updateHero(hero: Hero): Observable<Hero> {
return this.http.put<Hero>(this.heroesUrl, hero);
}
}
Por que usar serviços de dados é melhor?
- Permite que os componentes se concentrem na apresentação e interação com o usuário, delegando a lógica de manipulação de dados para serviços dedicados.
- Facilita a testabilidade dos componentes, permitindo a simulação ou uso real das chamadas de dados.
- Encapsula detalhes de gerenciamento de dados, permitindo que o serviço de dados evolua sem afetar os componentes que dependem dele.
Refatorar a lógica de operações de dados para serviços dedicados melhora a separação de responsabilidades, a testabilidade e a manutenção do código. Serviços de dados encapsulam detalhes de implementação, permitindo que os componentes se concentrem em sua responsabilidade principal: a apresentação.
Ganchos de Ciclo de Vida: Acompanhe os Momentos Cruciais da Vida de um Componente Angular
No ciclo de vida de um componente Angular, diversos eventos importantes ocorrem, desde sua criação até sua destruição. Os ganchos de ciclo de vida (lifecycle hooks) são como pontos de verificação que permitem que você execute ações em momentos específicos da vida do componente.
Implemente as Interfaces de Ganchos de Ciclo de Vida:
✅Faça: Implemente as interfaces de ganchos de ciclo de vida.
❓Por quê? As interfaces de ciclo de vida prescrevem assinaturas de métodos tipados. Use essas assinaturas para identificar erros de ortografia e sintaxe.
❌Exemplo Incorreto: Método ngOnInit
com Erro de Ortografia
/* Evite */
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">OK</button>`,
})
export class HeroButtonComponent {
onInit() {
// erro de ortografia
console.log('The component is initialized');
}
}
✅Exemplo Correto: Implementação da Interface OnInit
@Component({
standalone: true,
selector: 'toh-hero-button',
template: `<button type="button">OK</button>`,
})
export class HeroButtonComponent implements OnInit {
ngOnInit() {
console.log('The component is initialized');
}
}
No exemplo correto, a classe HeroButtonComponent
implementa a interface OnInit
, que garante que o método ngOnInit
seja chamado quando o componente for inicializado. Além disso, a interface ajuda a evitar erros de ortografia, como no exemplo incorreto.
Ao implementar as interfaces de ganchos de ciclo de vida, você estará seguindo as melhores práticas do Angular e garantindo a qualidade do seu código.
A Arte da Arquitetura Angular: Conclusão e Reflexões
Este guia de estilo para desenvolvimento em Angular é um recurso abrangente que abrange várias práticas recomendadas e diretrizes para escrever código eficiente, legível e sustentável. A seguir, estão os principais pontos abordados ao longo deste guia:
Estrutura de Arquivos e Convenções de Nomenclatura
Assim como um mestre artesão finaliza sua obra-prima com um toque final, chegamos ao fim deste guia de estilo Angular. Exploramos as melhores práticas e convenções, desde a organização dos arquivos até a comunicação entre componentes, passando pela nomenclatura, estrutura de módulos e muito mais.
Ao longo desta jornada, aprendemos que a arquitetura Angular é uma arte que exige atenção aos detalhes, disciplina e um olhar para o futuro. Ao seguir as diretrizes deste guia, você estará construindo aplicações mais legíveis, manuteníveis, escaláveis e, acima de tudo, elegantes.
Lembre-se que este guia é um ponto de partida. À medida que você ganha experiência e domina a arte da arquitetura Angular, poderá adaptar e personalizar as convenções para atender às necessidades específicas do seu projeto.
Assim como um mestre artesão nunca para de aprender e aprimorar suas habilidades, continue explorando as nuances do Angular, experimentando novas abordagens e buscando a excelência em cada linha de código.
Com este guia como seu mapa e a paixão pela arquitetura Angular como seu guia, você estará pronto para criar aplicações que são verdadeiras obras de arte da engenharia de software.
Este guia de estilo é um recurso essencial para desenvolvedores que buscam construir aplicações Angular robustas e eficientes. A aplicação consistente dessas práticas não apenas melhora a qualidade do código, mas também facilita a colaboração e a escalabilidade do projeto. Continue revisando e aprimorando suas habilidades, mantendo-se atualizado com as melhores práticas e evoluções da plataforma Angular.
Comment (1)
Comments are closed.
Pingback: Convenções de Código para Projetos Angular - Experts in Angular