← Angular: componentes, reatividade e decisões de aplicação
03 / 10 · 65 MIN

Injeção de dependências e ciclo de vida dos serviços

Escolher tokens, providers e âmbito de resolução a partir da identidade e duração do estado. Interpretar aliases, factories e serviços por componente sem confundir tipagem TypeScript com chaves em runtime.

Tokens têm identidade em runtime

A injeção procura uma chave e devolve o valor associado pelo provider. Uma classe pode servir de token porque existe em runtime. Uma interface TypeScript desaparece da saída JavaScript e não serve, por si só, como essa chave.

Para configurações ou contratos sem classe concreta, declara e exporta uma instância de InjectionToken. A descrição passada ao construtor ajuda o diagnóstico, mas não unifica objetos: duas chamadas new InjectionToken com a mesma descrição criam tokens diferentes.

Importa o mesmo token nos produtores e consumidores. O tipo genérico ajuda a tipagem, sem substituir a identidade do objeto.

Escolher a estratégia do provider

useClass pede ao sistema a criação de uma instância da classe indicada. useValue fornece um valor já existente, como um objeto de configuração; fornecer uma classe como valor não equivale a criar essa classe. useFactory calcula o valor, podendo receber dependências declaradas no array deps pela mesma ordem dos parâmetros.

Se a factory recebe configuração e logger, inverter deps troca os argumentos e pode gerar um resultado incorreto mesmo que os nomes dos parâmetros pareçam claros. A resolução não depende de adivinhar nomes de variáveis. Escolhe a estratégia a partir da origem real do valor e da responsabilidade de criação.

Aliases preservam uma instância

useExisting permite que um token seja um alias de outro provider existente. Num período de migração, o token LegacyLogger pode resolver o mesmo objeto fornecido para NewLogger. Se for usado useClass: NewLogger em ambos os providers, podem existir duas instâncias, com buffers ou contadores separados.

Copiar propriedades entre elas não restaura a identidade nem garante atualizações futuras. Para uma extensão com vários handlers, multi: true representa outro contrato: as contribuições são reunidas num array para o token. Não confundas essa agregação com um alias nem com uma política que devolve apenas o último handler.

A hierarquia define partilha

Na resolução normal, Angular procura primeiro no ElementInjector do elemento e sobe pelos elementos ancestrais. Se não encontrar um provider, procura na hierarquia EnvironmentInjector a partir da origem apropriada. A primeira correspondência aplicável determina o valor; providers locais podem sobrepor um provider disponível em root.

No exemplo, cada DraftEditor possui uma instância própria de DraftState. O filho DraftPreview encontra o provider do editor, porque não cria outro provider para o token. O outro editor é um irmão, não um ancestral.

Partilhar entre ambos exigiria mover a responsabilidade para um âmbito comum adequado.

Modificar a procura deliberadamente

As opções de inject alteram um contrato de resolução concreto. self limita a procura ao injector atual em vez de percorrer os ancestrais. optional permite devolver null quando não existe um resultado, evitando o erro de dependência ausente.

Com self e optional, um provider apenas no pai não passa a ser encontrado: o resultado local em falta continua a ser null. Esta distinção é útil para uma funcionalidade opcional que só deve existir quando declarada no próprio elemento. Antes de usar uma opção para esconder um erro, verifica se o desenho pretendia dependência obrigatória, herança ou isolamento.

Contexto de injeção é síncrono

inject pode ser usado em contextos documentados, como initializers de propriedades e construção de uma classe criada pelo sistema DI, ou numa factory de provider. Um método comum chamado mais tarde por um evento não ganha contexto apenas por pertencer a um componente. runInInjectionContext permite executar uma função com um injector, mas o contexto aplica-se à stack frame síncrona.

Um callback assíncrono ou o código depois de await não herda essa permissão. Resolve antes a dependência e conserva a referência quando apropriado, ou entra explicitamente num contexto válido no ponto de resolução.

Rever identidade e duração em conjunto

O exemplo usa estado privado gravável e uma vista readonly para concentrar alterações no serviço. Essa escolha não seria suficiente se o provider estivesse no âmbito errado: dois editores poderiam continuar a partilhar a mesma instância root.

A revisão deve seguir o token desde o consumidor até ao provider vencedor, confirmar se é uma criação ou alias e identificar o proprietário do âmbito. Quando o componente com provider local é destruído, a instância associada participa nesse ciclo de vida.

Recursos externos que o serviço inicia continuam a precisar de uma política explícita de limpeza.

// Reading example: Angular 22; application bootstrap is outside this excerpt.
import {Component, Injectable, inject, signal} from '@angular/core';

@Injectable()
export class DraftState {
  private readonly value = signal('');
  readonly text = this.value.asReadonly();
  setText(text: string) { this.value.set(text); }
}

@Component({
  selector: 'draft-preview', standalone: true,
  template: `<p>Preview: {{ state.text() }}</p>`,
})
export class DraftPreview { readonly state = inject(DraftState); }

@Component({
  selector: 'draft-editor', standalone: true, imports: [DraftPreview],
  providers: [DraftState],
  template: `<label>Draft <input #entry [value]="state.text()"
    (input)="state.setText(entry.value)"></label><draft-preview />`,
})
export class DraftEditor { readonly state = inject(DraftState); }

@Component({
  selector: 'draft-workspace', standalone: true, imports: [DraftEditor],
  template: `<draft-editor /><draft-editor />`,
})
export class DraftWorkspace {}
NA PRÁTICA

Cada DraftEditor declara o seu provider. O respetivo DraftPreview não declara outro, pelo que encontra a instância do editor na hierarquia. Escrever no primeiro editor atualiza a sua pré-visualização, sem alterar o segundo.

O estado é local e não representa uma gravação remota; destruir o editor termina o âmbito dessa instância.

Armadilhas comuns

Criar dois tokens com a mesma descrição; usar interfaces como tokens runtime; trocar useExisting por useClass; colocar estado por editor num serviço root; chamar inject depois de await; interpretar optional como pesquisa automática noutro âmbito.

Tópicos relacionados: Componentes, templates e identidade da vista · Signals, inputs e contratos de outputs · Ciclo de vida, efeitos e limpeza reativa

Leva esta ideia contigo

Segue quatro perguntas: qual é o token, como é criado o valor, onde começa a procura e quem termina a sua duração? Uma resposta explícita a cada uma permite distinguir configuração incorreta, partilha não desejada e uso fora do contexto de injeção.

Criar conta

Referência: Token runtime identity · Angular 22; standalone components