Capítulo 09: Injeção de Dependências Hierárquica e a Função inject()

Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR


🗺️ Mapa Conceitual da Árvore Hierárquica de Injeção de Dependências

flowchart TD
    Platform["PlatformInjector (Plataforma Compartilhada)"] --> Root["EnvironmentInjector (providedIn: 'root')"]
    Root --> Route["Route / Lazy-Loaded EnvironmentInjector"]
    Route --> CompParent["ElementInjector (Componente Pai - providers: [...])"]
    CompParent --> CompChild["ElementInjector (Componente Filho)"]

    subgraph INJECTION_API["Modern Dependency Injection (Angular 18+)"]
        A["inject(Token, { optional: true })"]
        B["InjectionToken<T>"]
        C["Functional Injection (Composables / Custom Hooks)"]
    end

    CompChild -.-> INJECTION_API

    style Platform fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style Root fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style Route fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style CompParent fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style INJECTION_API fill:#fce4ec,stroke:#e91e63,stroke-width:2px

🏛️ 1. O Sistema de Injeção de Dependências (DI) do Angular

O sistema de Injeção de Dependências do Angular é um dos mais sofisticados da indústria de software. Ele é hierárquico, o que significa que diferentes partes da árvore de componentes podem receber instâncias isoladas ou compartilhadas de um mesmo serviço, dependendo do local onde o provedor (Provider) foi declarado.

1.1 A Revolução da Função inject()

Tradicionalmente, a injeção era feita via parâmetros no construtor da classe (constructor(private svc: MeuService)). No Angular 14 e consolidado no Angular 18+, a função inject() assumiu o protagonismo:

  • Redução de Boilerplate: Não exige repetição de modificadores de acesso nem construtores vazios.
  • Herança Simples: Elimina a necessidade de passar serviços pelo super(svc1, svc2, ...) em classes derivadas.
  • Injeção Funcional Componível: Permite criar funções reutilizáveis (semelhantes a composables no Vue ou Custom Hooks no React) que usam DI internamente.

⚙️ 2. Prática com inject() e Modificadores de Resolução

A função inject() aceita a classe do serviço ou um InjectionToken, além de opções que controlam a busca na árvore hierárquica:

import { Component, inject, InjectionToken, Host, Optional, Self, SkipSelf } from '@angular/core';

// 1. Criação de um InjectionToken tipado para configurações da aplicação
export interface AppConfig {
  apiUrl: string;
  timeoutMs: number;
}
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG', {
  providedIn: 'root',
  factory: () => ({ apiUrl: 'https://api.empresa.com/v1', timeoutMs: 5000 })
});

// 2. Serviço com escopo Global (Singleton)
import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class SessaoService {
  usuario = signal<{ nome: string; role: string } | null>({ nome: 'Carlos', role: 'admin' });
}

// 3. Componente consumindo com inject()
@Component({
  selector: 'app-dashboard-header',
  standalone: true,
  template: `
    <header>
      <h1>Painel de Controle</h1>
      <p>Usuário Conectado: {{ sessao.usuario()?.nome }}</p>
      <small>Conectado a: {{ config.apiUrl }}</small>
    </header>
  `
})
export class DashboardHeaderComponent {
  // Injeção limpa e direta como propriedade de classe
  readonly sessao = inject(SessaoService);
  readonly config = inject(APP_CONFIG);

  // Injeção opcional: não quebra se o serviço não estiver presente
  private logOpcional = inject(ConsoleLogService, { optional: true });
}

class ConsoleLogService {}

🧩 3. Padrão de Injeção Funcional (Custom Composable Functions)

Com a função inject(), é possível extrair comportamentos complexos para fora das classes de componentes, criando utilitários reativos modulares:

// composables/use-window-size.ts
import { inject, signal, DestroyRef } from '@angular/core';

export function useWindowSize() {
  const destroyRef = inject(DestroyRef);
  const width = signal(window.innerWidth);
  const height = signal(window.innerHeight);

  const handler = () => {
    width.set(window.innerWidth);
    height.set(window.innerHeight);
  };

  window.addEventListener('resize', handler);
  destroyRef.onDestroy(() => window.removeEventListener('resize', handler));

  return { width: width.asReadonly(), height: height.asReadonly() };
}

Consumo em qualquer componente com uma única linha:

@Component({
  selector: 'app-responsivo',
  standalone: true,
  template: `<p>Largura Atual: {{ windowSize.width() }}px</p>`
})
export class ResponsivoComponent {
  readonly windowSize = useWindowSize(); // Injeta e gerencia ciclo de vida automaticamente!
}

🌳 4. Os Níveis Hierárquicos de Injeção

Nível Hierárquico Onde é Declarado Ciclo de Vida da Instância
Root (Environment) @Injectable({ providedIn: 'root' }) ou appConfig.providers Singleton Único por toda a vida da aplicação
Route Scope No array providers de uma rota no routes.ts Criado ao navegar na rota e destruído ao sair dela
Component Level @Component({ providers: [MeuService] }) Instância Única por Componente (compartilhada com filhos)
ViewProviders @Component({ viewProviders: [...] }) Isolada da projeção de conteúdo (<ng-content>)

⚠️ 5. Armadilhas Comuns e Anti-Patterns

  1. Chamar inject() Fora do Injection Context:
    • Erro: Tentar chamar inject(MeuService) dentro de um método de evento como onClick().
    • Causa: O Angular só disponibiliza o contexto de injeção durante a instanciação da classe (no construtor ou na inicialização de campos).
    • Solução: Guarde a referência em um campo da classe: private meuSvc = inject(MeuService); e use no método: this.meuSvc.executar().
  2. Duplicação Acidental de Instâncias com providers no Componente:
    • Se você colocar um serviço singleton no array providers: [MeuService] de um componente, aquele componente criará uma nova instância independente, quebrando o estado compartilhado da aplicação.

🛡️ 6. Boas Práticas e Arquitetura Corporativa

  • Prefira providedIn: 'root' para Quase Tudo: Ele habilita tree-shaking automático: se nenhum componente usar o serviço, ele é removido do bundle final pelo compilador.
  • Use InjectionToken para Configurações e Ambientes: Nunca consuma objetos de ambiente (environment.ts) diretamente em serviços de baixo nível. Injete-os via InjectionToken para viabilizar testes unitários e mocks independentes.

📋 7. Cheat-Sheet: Resumo Executivo

// 1. Injeção direta
const svc = inject(MeuService);

// 2. Modificadores
const opcional = inject(LoggerService, { optional: true });
const proprio = inject(LocalService, { self: true });
const pularProprio = inject(ParentService, { skipSelf: true });

// 3. InjectionToken
const API_URL = new InjectionToken<string>('API_URL');
const url = inject(API_URL);