Injeção de Dependências Avançada no Angular 2026: Providers, Tokens e Perguntas de Entrevista

Dominar a injeção de dependências no Angular com InjectionToken, injetores hierárquicos, modificadores de resolução e multi-providers. Inclui perguntas de entrevista e padrões de produção.

Injeção de Dependências Avançada no Angular 2026

O sistema de injeção de dependências do Angular oferece controle refinado sobre a instanciação de serviços através de providers, tokens e injetores hierárquicos. Dominar esses conceitos diferencia desenvolvedores Angular experientes dos juniores em entrevistas técnicas e bases de código em produção.

Conceito Chave

O Angular mantém duas árvores de injetores paralelas: a árvore ModuleInjector para serviços fornecidos no nível do módulo, e a árvore ElementInjector para dependências com escopo de componente. A resolução começa no nível do elemento e sobe até a raiz.

Estratégias de Configuração de Providers no Angular DI

O Angular oferece múltiplas estratégias de configuração de providers, cada uma adequada para diferentes casos de uso. As configurações mais comuns usam useClass, useValue, useFactory e useExisting.

app.config.tstypescript
import { ApplicationConfig, InjectionToken } from '@angular/core';
import { LoggerService } from './services/logger.service';
import { DebugLoggerService } from './services/debug-logger.service';
import { API_CONFIG, ApiConfig } from './config/api.config';

export const appConfig: ApplicationConfig = {
  providers: [
    // useClass: fornecer uma implementação diferente
    { provide: LoggerService, useClass: DebugLoggerService },
    
    // useValue: fornecer um objeto de configuração estático
    { 
      provide: API_CONFIG, 
      useValue: { baseUrl: 'https://api.example.com', timeout: 5000 } 
    },
    
    // useFactory: criar dependência com lógica em tempo de execução
    {
      provide: 'FEATURE_FLAGS',
      useFactory: () => {
        const env = import.meta.env.MODE;
        return { debugMode: env === 'development', analytics: env === 'production' };
      }
    },
    
    // useExisting: criar um alias para outro provider
    { provide: 'Logger', useExisting: LoggerService }
  ]
};

A estratégia useClass troca implementações sem alterar o código consumidor. A estratégia useValue fornece objetos estáticos como configuração. A estratégia useFactory lida com decisões em tempo de execução, e useExisting cria aliases para acesso polimórfico.

InjectionToken para Dependências Não-Classe com Type Safety

Enquanto @Injectable funciona para serviços baseados em classes, valores não-classe como objetos de configuração, primitivos ou funções requerem InjectionToken. Este token age como uma chave única no registro DI do Angular.

tokens/config.tokens.tstypescript
import { InjectionToken } from '@angular/core';

export interface ApiConfig {
  baseUrl: string;
  timeout: number;
  retryAttempts: number;
}

// O parâmetro genérico garante type safety no ponto de injeção
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config', {
  providedIn: 'root',
  factory: () => ({
    baseUrl: 'https://api.sharpskill.dev',
    timeout: 30000,
    retryAttempts: 3
  })
});

// Token para valores primitivos
export const MAX_UPLOAD_SIZE = new InjectionToken<number>('max.upload.size', {
  providedIn: 'root',
  factory: () => 10 * 1024 * 1024 // 10MB
});

O parâmetro de tipo genérico em InjectionToken<ApiConfig> se propaga para a chamada inject(). O TypeScript sabe que o valor injetado corresponde ao tipo declarado, detectando erros de uso em tempo de compilação ao invés de tempo de execução.

A Função inject() vs Injeção por Construtor

O Angular 14 introduziu a função inject() como alternativa à injeção baseada em construtor. No Angular 20+, inject() se tornou a abordagem preferida, especialmente em componentes standalone e contextos funcionais.

user.service.tstypescript
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_CONFIG } from '../tokens/config.tokens';

@Injectable({ providedIn: 'root' })
export class UserService {
  // Abordagem moderna: inject() no nível do campo
  private readonly http = inject(HttpClient);
  private readonly config = inject(API_CONFIG);

  getUser(id: string) {
    return this.http.get(`${this.config.baseUrl}/users/${id}`);
  }
}

// Alternativa: injeção por construtor (ainda válida)
@Injectable({ providedIn: 'root' })
export class UserServiceLegacy {
  constructor(
    private readonly http: HttpClient,
    @Inject(API_CONFIG) private readonly config: ApiConfig
  ) {}
}

A função inject() elimina o boilerplate de decoradores para tokens e habilita a injeção de dependências em contextos não-classe como guards, resolvers e interceptors funcionais.

auth.guard.tstypescript
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

// Guard funcional usando inject()
export const authGuard: CanActivateFn = () => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isAuthenticated()) {
    return true;
  }
  return router.createUrlTree(['/login']);
};

A função inject() só funciona dentro de um contexto de injeção: durante a construção da classe, em funções factory, ou em construções funcionais do Angular. Chamá-la fora desses contextos lança um erro em tempo de execução.

Pronto para mandar bem nas entrevistas de Angular?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Injetores Hierárquicos: ElementInjector vs EnvironmentInjector

O Angular mantém duas hierarquias de injetores paralelas que determinam o escopo do serviço e a ordem de resolução. Entender esta arquitetura é essencial para controlar o tempo de vida e a visibilidade dos serviços.

data.service.tstypescript
import { Injectable } from '@angular/core';

// Singleton no nível raiz: uma única instância em toda a app
@Injectable({ providedIn: 'root' })
export class GlobalDataService {
  private data = new Map<string, unknown>();
  
  set(key: string, value: unknown) { this.data.set(key, value); }
  get(key: string) { return this.data.get(key); }
}

// Com escopo de componente: nova instância por componente
@Injectable()
export class ComponentDataService {
  private data = new Map<string, unknown>();
  
  set(key: string, value: unknown) { this.data.set(key, value); }
  get(key: string) { return this.data.get(key); }
}
dashboard.component.tstypescript
import { Component } from '@angular/core';
import { ComponentDataService } from './services/component-data.service';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  // Este componente e todos os filhos obtêm a mesma instância
  providers: [ComponentDataService],
  template: `
    <app-widget />
    <app-stats />
  `
})
export class DashboardComponent {}

Quando um componente declara um provider, o Angular cria uma nova instância com escopo no ElementInjector desse componente. Componentes filhos herdam acesso aos providers do pai a menos que declarem os seus próprios.

O algoritmo de resolução segue este caminho:

  1. Verificar o ElementInjector do componente solicitante
  2. Subir pela árvore ElementInjector até os ancestrais
  3. Verificar o EnvironmentInjector (módulo ou providers standalone)
  4. Subir até o EnvironmentInjector raiz
  5. Chegar ao NullInjector e lançar um erro se @Optional() não foi usado

Modificadores de Resolução: @Self, @SkipSelf, @Optional, @Host

Os modificadores de resolução alteram como o Angular busca na hierarquia de injetores. Esses decoradores funcionam tanto com injeção por construtor quanto com a função inject().

panel.component.tstypescript
import { Component, Optional, SkipSelf, Self, inject } from '@angular/core';
import { PanelService } from './panel.service';

@Component({
  selector: 'app-panel',
  standalone: true,
  providers: [PanelService],
  template: `<ng-content />`
})
export class PanelComponent {
  // @Self: só buscar no injetor deste componente
  private readonly localService = inject(PanelService, { self: true });
  
  // @SkipSelf: pular este componente, iniciar busca a partir do pai
  private readonly parentService = inject(PanelService, { 
    skipSelf: true, 
    optional: true 
  });
  
  // @Optional: retornar null ao invés de lançar erro se não encontrado
  private readonly optionalService = inject(PanelService, { optional: true });

  constructor() {
    // localService é sempre a instância própria do componente
    // parentService é a instância do pai ou null
    console.log('Local:', this.localService);
    console.log('Parent:', this.parentService);
  }
}

O modificador @Host() restringe a resolução ao injetor do elemento host e para nos limites do componente. Isso é útil quando uma diretiva precisa acessar um serviço fornecido pelo seu componente host mas não deve alcançar mais acima na árvore.

highlight.directive.tstypescript
import { Directive, inject, Host, Optional } from '@angular/core';
import { HighlightConfig } from './highlight.config';

@Directive({
  selector: '[appHighlight]',
  standalone: true
})
export class HighlightDirective {
  // Só buscar nos providers do componente host
  private readonly config = inject(HighlightConfig, { 
    host: true, 
    optional: true 
  });

  constructor() {
    // config é null se o componente host não forneceu HighlightConfig
    const color = this.config?.color ?? 'yellow';
    // Aplicar destaque...
  }
}

Multi-Providers para Sistemas Extensíveis

Os multi-providers permitem registrar múltiplos valores sob um único token. O Angular retorna todos os valores registrados como um array, habilitando arquiteturas de plugins e padrões de extensibilidade.

validators.tokens.tstypescript
import { InjectionToken } from '@angular/core';

export interface Validator {
  validate(value: string): string | null;
}

export const VALIDATORS = new InjectionToken<Validator[]>('validators');
app.config.tstypescript
import { ApplicationConfig } from '@angular/core';
import { VALIDATORS } from './validators.tokens';

const requiredValidator = {
  validate: (value: string) => value ? null : 'O campo é obrigatório'
};

const minLengthValidator = {
  validate: (value: string) => value.length >= 3 ? null : 'Mínimo de 3 caracteres'
};

const emailValidator = {
  validate: (value: string) => 
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : 'Formato de email inválido'
};

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: VALIDATORS, useValue: requiredValidator, multi: true },
    { provide: VALIDATORS, useValue: minLengthValidator, multi: true },
    { provide: VALIDATORS, useValue: emailValidator, multi: true }
  ]
};
validation.service.tstypescript
import { Injectable, inject } from '@angular/core';
import { VALIDATORS, Validator } from './validators.tokens';

@Injectable({ providedIn: 'root' })
export class ValidationService {
  private readonly validators = inject(VALIDATORS);

  validate(value: string): string[] {
    // validators é um array de todos os validadores registrados
    return this.validators
      .map(v => v.validate(value))
      .filter((error): error is string => error !== null);
  }
}

A flag multi: true indica ao Angular para coletar todos os providers para este token em um array. Sem ela, providers posteriores sobrescreveriam os anteriores.

Perguntas de Entrevista sobre Injeção de Dependências no Angular

Entrevistas técnicas frequentemente avaliam o entendimento do sistema DI do Angular. Estas são perguntas que distinguem candidatos com experiência em produção.

P: O que acontece quando se fornece o mesmo serviço tanto no nível do módulo quanto do componente?

O provider no nível do componente cria uma instância separada com escopo na subárvore desse componente. Serviços injetados nessa subárvore recebem a instância do componente, não o singleton do nível do módulo. Isso habilita isolamento de estado, por exemplo quando cada aba precisa do seu próprio estado de formulário.

P: Por que usar InjectionToken ao invés de uma string literal?

Tokens string correm risco de colisões entre bibliotecas ou diferentes partes de uma aplicação. InjectionToken cria uma referência única em tempo de execução que não pode entrar em conflito. O parâmetro de tipo genérico também fornece type safety em tempo de compilação que tokens string não possuem.

P: Quando inject() lança erro vs retorna undefined?

Por padrão, inject() lança quando a dependência não é encontrada. Passar { optional: true } muda o tipo de retorno para T | null e retorna null ao invés de lançar. Isso corresponde ao comportamento do decorador @Optional().

P: Explique a diferença entre providedIn: 'root' e fornecer no array providers de um módulo.

Ambos criam singletons, mas providedIn: 'root' habilita tree-shaking. O serviço só é incluído no bundle se realmente for injetado em algum lugar. Providers no nível do módulo são sempre incluídos independentemente do uso.

P: Como o lazy loading afeta o escopo do serviço?

Módulos lazy-loaded obtêm seu próprio EnvironmentInjector filho. Serviços fornecidos em um módulo lazy têm escopo nesse módulo e seus filhos. Um serviço com providedIn: 'root' permanece como um verdadeiro singleton através de todos os módulos, lazy ou não.

Para perguntas práticas sobre serviços Angular e padrões DI, confira as perguntas de entrevista Angular sobre serviços e injeção de dependências.

Padrões Práticos: Configuração e Feature Flags

Aplicações reais combinam esses conceitos DI para gerenciamento de configuração. Este padrão usa InjectionToken, factory providers e consciência do ambiente.

feature-flags.config.tstypescript
import { InjectionToken, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

export interface FeatureFlags {
  newCheckout: boolean;
  darkMode: boolean;
  betaFeatures: boolean;
}

export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('feature.flags', {
  providedIn: 'root',
  factory: () => {
    const platformId = inject(PLATFORM_ID);
    
    if (!isPlatformBrowser(platformId)) {
      // SSR: retornar valores padrão seguros
      return { newCheckout: false, darkMode: false, betaFeatures: false };
    }

    // Navegador: verificar localStorage ou config remota
    const stored = localStorage.getItem('featureFlags');
    if (stored) {
      return JSON.parse(stored);
    }
    
    return { newCheckout: true, darkMode: false, betaFeatures: false };
  }
});
feature-flag.directive.tstypescript
import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';
import { FEATURE_FLAGS } from './feature-flags.config';

@Directive({
  selector: '[appFeatureFlag]',
  standalone: true
})
export class FeatureFlagDirective {
  private readonly flags = inject(FEATURE_FLAGS);
  private readonly templateRef = inject(TemplateRef<unknown>);
  private readonly viewContainer = inject(ViewContainerRef);

  @Input() set appFeatureFlag(flag: keyof typeof this.flags) {
    if (this.flags[flag]) {
      this.viewContainer.createEmbeddedView(this.templateRef);
    } else {
      this.viewContainer.clear();
    }
  }
}

Esta diretiva renderiza condicionalmente conteúdo baseado em feature flags, com a configuração de flags centralizada em um único token injetável.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Práticas DI Angular Prontas para Produção

  • Usar providedIn: 'root' para singletons no nível da aplicação que se beneficiam de tree-shaking
  • Preferir inject() sobre injeção por construtor no Angular 20+ para sintaxe mais limpa e compatibilidade funcional
  • Escopar serviços com estado para componentes quando isolamento é necessário, não no nível do módulo
  • Criar InjectionToken para dependências não-classe para garantir type safety e evitar colisões
  • Aplicar @Optional() quando uma dependência pode não existir, especialmente para plugins ou recursos opcionais
  • Testar componentes com providers sobrescritos usando TestBed.overrideComponent() para isolamento
  • Usar multi-providers para padrões de extensibilidade como validadores, interceptors e handlers
Desafio do dia

Você saberia encontrar o bug em Angular?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 13 de setembro de 2026

Tags

#angular
#dependency-injection
#typescript
#interview

Compartilhar

Artigos relacionados