DEV Community

Migrando um YMS de 5 anos: Do Angular 16 ao 21 (Parte 3) - A Revolução Silenciosa da Função inject()

Fala, comunidade dev! 👋

Na segunda parte da nossa série sobre a modernização de um sistema corporativo (saindo do Angular 16 e indo direto para o 21), vimos como o roteamento ficou muito mais limpo com o fim das antigas classes Guards. Mas eu deixei um gancho no ar: essa limpeza nas rotas só foi possível graças a uma pequena função que mudou para sempre a forma como escrevemos Angular.

Hoje vamos falar da verdadeira revolução silenciosa do framework: a função inject().


1. O Pesadelo do Construtor em Sistemas Corporativos

Quem trabalha com aplicações Enterprise sabe que uma única tela muitas vezes precisa se comunicar com diversas partes do sistema.

Imagine um cenário clássico que todo desenvolvedor já enfrentou: uma tela complexa de Cadastro de Usuários. Para essa tela funcionar, você precisa buscar dados do usuário, validar o CEP em uma API externa, checar permissões, montar o formulário e gerenciar alertas na tela.

No Angular 16, a única forma de trazer essas dependências para dentro do componente era através do constructor. O resultado era um bloco de código gigantesco, onde a injeção de dependência engolia a lógica de negócio:

O Antes (Poluição visual no Angular 16):

@Component({ ... })
export class CadastroUsuarioComponent {
  constructor(
    private usuarioService: UsuarioService,
    private viaCepService: ViaCepService,
    private permissoesService: PermissoesService,
    private fb: FormBuilder,
    private router: Router
  ) { }
}
Enter fullscreen mode Exit fullscreen mode

Metade do arquivo do componente era apenas boilerplate (código repetitivo) para injetar dependências. Mas o problema real nem era a estética. O verdadeiro inferno começava quando precisávamos usar herança.


2. O Fim do "Inferno do super()"

Em arquiteturas limpas, é extremamente comum criarmos classes base para reaproveitar lógica. Um exemplo perfeito é um BaseFormComponent que concentra a lógica de exibição de toasts de erro, spinners de loading e validações genéricas.

A dor da herança antes do inject():
Se o seu BaseFormComponent precisasse injetar o MessageService e o LoadingService, todos os componentes filhos que estendessem essa classe precisavam injetar esses serviços em seus próprios construtores, apenas para passá-los para cima através da chamada do super().

// O componente filho precisava resolver dependências que não eram dele!
constructor(
  private usuarioService: UsuarioService,
  private fb: FormBuilder,
  // O componente filho nem usa esses dois, mas precisa injetar para o pai!
  private messageService: MessageService, 
  private loadingService: LoadingService
) {
  super(messageService, loadingService); 
}
Enter fullscreen mode Exit fullscreen mode

Se um dia você precisasse adicionar mais um serviço genérico na classe base, você quebrava a aplicação inteira. Era necessário abrir e refatorar o construtor de dezenas (ou centenas) de telas filhas.


3. A Chegada da Injeção Baseada em Propriedades

Com a maturidade da função inject(), a injeção de dependências deixou de ser uma exclusividade do construtor e passou a ser declarada diretamente na inicialização das propriedades da classe.

O Depois (Limpo e escalável no Angular 21):

@Component({ ... })
export class CadastroUsuarioComponent extends BaseFormComponent {
  // A injeção agora vai direto na propriedade
  private usuarioService = inject(UsuarioService);
  private viaCepService = inject(ViaCepService);
  private fb = inject(FormBuilder);

  // O constructor sumiu! 
  // O BaseFormComponent se vira sozinho para buscar suas próprias dependências.
}
Enter fullscreen mode Exit fullscreen mode

Nessa nova abordagem, a classe base (BaseFormComponent) usa o inject() internamente para pegar seus próprios serviços de alerta e loading. O componente filho não precisa mais saber (nem repassar) o que o pai está consumindo. O acoplamento despenca, e a refatoração vira uma tarefa segura e simples.


4. Fora das Classes: Interceptors Funcionais

Além de salvar a nossa sanidade na hora de estender classes, o inject() abriu as portas para o Angular ser muito mais funcional.

Assim como as rotas, os velhos HttpInterceptors (que interceptam requisições de API e também eram classes verbosas) viraram funções puras. Em qualquer sistema corporativo, injetar o Bearer Token de autenticação em todas as chamadas HTTP é regra. Olha como esse código ficou absurdamente simples e direto agora:

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService); // Injeção fora de uma classe!
  const token = authService.getToken();

  const requestModificada = req.clone({
    setHeaders: { Authorization: `Bearer ${token}` }
  });

  return next(requestModificada);
};
Enter fullscreen mode Exit fullscreen mode

Conclusão

Adotar o inject() em todo o projeto reduz drasticamente a quantidade de linhas de código, melhora a legibilidade e blinda o seu sistema contra refatorações dolorosas em classes base. É uma mudança de paradigma que separa os projetos legados engessados dos códigos modernos e fluidos.

E por falar em reduzir código, no nosso próximo artigo da série (Parte 4), vamos sair do TypeScript e olhar para o HTML. Vamos dar o adeus definitivo ao *ngIf e ao *ngFor, e entender como o novo Control Flow e a mágica do @defer otimizam a renderização de telas pesadas.

Como está a adoção do inject() nos projetos de vocês? Ainda preferem o bom e velho construtor ou já abraçaram as propriedades dinâmicas? Comentem aí! 👇

Top comments (0)