Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 09 — Injeção de Dependências com inject() e InjectionToken Tipado

Este exemplo apresenta o modelo moderno de Injeção de Dependências (DI) do Angular 18+, utilizando a função funcional inject() (substituindo construtores verbosos com dezenas de parâmetros) e InjectionToken tipado para injeção de configurações imutáveis da aplicação.

1. Token de Configuração e Serviço: src/app/services/telemetry.service.ts

// src/app/services/telemetry.service.ts
import { Injectable, InjectionToken, inject, signal } from '@angular/core';

export interface AppConfig {
  apiEndpoint: string;
  enableDebugLogging: boolean;
  maxRetries: number;
}

// 1. InjectionToken com factory padrão e tipagem estrita
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG', {
  providedIn: 'root',
  factory: () => ({
    apiEndpoint: 'https://telemetry.empresa.com.br/v2',
    enableDebugLogging: true,
    maxRetries: 3
  })
});

export interface TelemetryLog {
  id: string;
  level: 'INFO' | 'WARN' | 'ERROR';
  message: string;
  timestamp: string;
}

// 2. Serviço com providedIn: 'root' (Singleton por aplicação)
@Injectable({
  providedIn: 'root'
})
export class TelemetryService {
  // Injeção funcional limpa com inject()
  private config = inject(APP_CONFIG);

  logs = signal<TelemetryLog[]>([]);

  recordLog(level: 'INFO' | 'WARN' | 'ERROR', message: string): void {
    if (level === 'INFO' && !this.config.enableDebugLogging) return;

    const entry: TelemetryLog = {
      id: `log-${Date.now()}-${Math.floor(Math.random() * 1000)}`,
      level,
      message,
      timestamp: new Date().toLocaleTimeString()
    };

    this.logs.update((curr) => [entry, ...curr]);
    console.log(`[Telemetry Service] [${this.config.apiEndpoint}]`, entry);
  }

  clearLogs(): void {
    this.logs.set([]);
  }
}

2. Componente Consumidor: src/app/components/service-injector-demo.component.ts

// src/app/components/service-injector-demo.component.ts
import { Component, inject } from '@angular/core';
import { TelemetryService, APP_CONFIG } from '../services/telemetry.service';

@Component({
  selector: 'app-service-injector-demo',
  standalone: true,
  template: `
    <div class="max-w-xl mx-auto p-6 bg-white border border-slate-200 rounded-2xl shadow-sm space-y-6">
      <div class="border-b pb-4">
        <h2 class="text-xl font-bold text-slate-800">💉 Injeção de Dependências com inject()</h2>
        <p class="text-xs text-slate-500">Eliminando constructors gigantescos com a API funcional</p>
      </div>

      <!-- Configuração Injetada via InjectionToken -->
      <div class="p-4 bg-slate-50 border rounded-xl text-xs space-y-1">
        <p><strong>Endpoint:</strong> <code class="font-mono text-indigo-600">{{ config.apiEndpoint }}</code></p>
        <p><strong>Debug Logging:</strong> {{ config.enableDebugLogging ? 'Ativado' : 'Desativado' }}</p>
        <p><strong>Retries Máximos:</strong> {{ config.maxRetries }}</p>
      </div>

      <!-- Botões de Ação para Disparo de Logs -->
      <div class="flex gap-2">
        <button
          type="button"
          (click)="logInfo()"
          class="flex-1 py-2 bg-blue-50 text-blue-700 hover:bg-blue-100 rounded-xl text-xs font-bold transition-colors"
        >
          + Log Info
        </button>
        <button
          type="button"
          (click)="logWarning()"
          class="flex-1 py-2 bg-amber-50 text-amber-700 hover:bg-amber-100 rounded-xl text-xs font-bold transition-colors"
        >
          + Log Warning
        </button>
        <button
          type="button"
          (click)="logError()"
          class="flex-1 py-2 bg-red-50 text-red-700 hover:bg-red-100 rounded-xl text-xs font-bold transition-colors"
        >
          + Log Erro
        </button>
      </div>

      <!-- Exibição Reativa dos Logs da Store de Telemetria -->
      <div class="space-y-2">
        <div class="flex justify-between items-center text-xs">
          <span class="font-bold text-slate-600 uppercase">Trilha de Auditoria (Signal no Serviço):</span>
          <button (click)="telemetry.clearLogs()" class="text-slate-400 hover:text-slate-700 underline">
            Limpar
          </button>
        </div>

        <div class="p-3 bg-slate-900 text-slate-100 font-mono text-xs rounded-xl max-h-48 overflow-y-auto space-y-1">
          @for (log of telemetry.logs(); track log.id) {
            <div class="flex justify-between items-center border-b border-slate-800 pb-1">
              <span>
                <strong
                  [class.text-blue-400]="log.level === 'INFO'"
                  [class.text-amber-400]="log.level === 'WARN'"
                  [class.text-red-400]="log.level === 'ERROR'"
                >
                  [{{ log.level }}]
                </strong>
                {{ log.message }}
              </span>
              <span class="text-slate-500 text-[10px]">{{ log.timestamp }}</span>
            </div>
          } @empty {
            <div class="text-slate-500">// Nenhum log registrado ainda. Dispare uma ação...</div>
          }
        </div>
      </div>
    </div>
  `
})
export class ServiceInjectorDemoComponent {
  // Injeção de dependências sem declarar constructor!
  readonly telemetry = inject(TelemetryService);
  readonly config = inject(APP_CONFIG);

  logInfo(): void {
    this.telemetry.recordLog('INFO', 'Sessão do operador confirmada com token JWT.');
  }

  logWarning(): void {
    this.telemetry.recordLog('WARN', 'Latência de rede no cluster sa-east-1 ultrapassou 180ms.');
  }

  logError(): void {
    this.telemetry.recordLog('ERROR', 'Falha na conexão com réplica do banco de leitura.');
  }
}

🖥️ Saída / Comportamento Esperado

Renderização Inicial:
Endpoint: https://telemetry.empresa.com.br/v2
Debug Logging: Ativado | Retries Máximos: 3
Terminal de logs: "// Nenhum log registrado ainda..."

Ao clicar em "+ Log Info":
O método telemetry.recordLog() muta o signal telemetry.logs no serviço singleton.
Linha inserida no terminal:
"[INFO] Sessão do operador confirmada com token JWT.  [14:48:10]"
Console log exibe:
"[Telemetry Service] [https://telemetry.empresa.com.br/v2] { id: 'log-...', level: 'INFO', ... }"

Ao clicar em "+ Log Erro":
Linha destacada em vermelho [ERROR] é inserida no topo.
Ao recarregar o componente em outra rota, o singleton permanece íntegro e reutilizado.

🔍 Destaques da Implementação

  1. Adeus ao Constructor Boilerplate: A função inject(Token) pode ser atribuída diretamente a propriedades readonly de classe, facilitando herança e composição.
  2. InjectionToken com Factory: Garante provimento de valor padrão imediato sem poluir a lista de providers globais do main.ts.
  3. Hierarquia de Injetores: O Angular resolve a injeção navegando do injetor do componente até o injetor raiz da aplicação (root).