NG0950: lendo um input obrigatório antes do Angular preencher

O NG0950: Required input is accessed before a value is set aparece quando você lê um signal input.required() cedo demais. A doc é exata: os inputs só estão garantidos a partir do ngOnInit, nunca no construtor. Então leia o input obrigatório no ngOnInit, num computed ou num effect, ou conserte o componente pai que esqueceu o binding.

O erro, em uma linha

A mensagem completa é NG0950: Required input is accessed before a value is set, e a descrição oficial é uma frase só: um input obrigatório foi acessado mas nenhum valor foi vinculado. O signal input obrigatório, declarado com input.required<T>(), não tem valor inicial por desenho. Quando você lê um deles antes do binding do pai ser aplicado, não há nada pra retornar, e o Angular lança o erro em vez de te entregar undefined.

Os signal inputs chegaram no Angular 17.1 (em developer preview na época), e o input.required() é a variante sem fallback. O ganho é justamente esse: o tipo é T, nunca T | undefined, e o framework garante que o valor existe depois que o componente está montado. O NG0950 é o framework mantendo essa promessa honesta. Você leu o valor num momento em que a garantia ainda não vale, e ele te avisa em vez de deixar um undefined silencioso vazar pelos seus tipos.

Quando o input está disponível de verdade

A doc declara o timing sem rodeio: os inputs estão garantidos no hook ngOnInit e dali pra frente. O construtor roda antes de o Angular aplicar os bindings, e os inicializadores de campo rodam como parte da construção, então os dois executam enquanto o input obrigatório ainda está vazio. Ler ali é ler antes de o valor existir.

Essa é a parte que pega quem vem da era dos decorators. Com o @Input(), um campo ficava undefined até ser setado, e ler cedo devolvia undefined calado. O signal input obrigatório não tem esse estado intermediário, então a mesma leitura precoce que antes passava em silêncio agora lança o erro. O conserto nunca é enfraquecer o input, e sim lê-lo num ponto em que o binding já rodou.

Onde você lêO binding já rodou?Seguro ler um input obrigatório?
Inicializador de campoNão, roda durante a construçãoNão, lança NG0950
construtorNão, roda antes dos bindingsNão, lança NG0950
computed(() => ...)Sim, a derivação roda na primeira leituraSim
effect(() => ...)Sim, roda durante o change detectionSim
ngOnInit e hooks seguintesSim, garantido disponívelSim
Expressão no templateSim, avaliada no change detectionSim

Duas causas que parecem idênticas

O NG0950 tem exatamente duas raízes, e a mensagem é a mesma pras duas, e é por isso que confunde. A primeira é timing: o binding está lá no pai, mas o seu código lê o input antes de ele ser aplicado. A doc nomeia o caso mais comum direto, que isso costuma acontecer quando o input é lido como parte da construção da classe. O conserto é mudar o lugar da leitura.

A segunda é binding ausente: o template do pai esqueceu o atributo, então nenhum valor foi vinculado e não há nada pra esperar. Aqui, mudar o lugar da leitura não resolve nada, porque o input segue vazio a vida inteira do componente. Antes de reorganizar o código de ciclo de vida, confirme que o pai de fato passa o input. As duas causas pedem consertos opostos, então dizer qual delas você tem é o primeiro passo real.

O mesmo input lido cedo demais vs lido no lugar certots
@Component({ selector: 'order-summary', /* ... */ })
export class OrderSummary {
  // Input obrigatório: o tipo é Order, nunca Order | undefined
  readonly order = input.required<Order>();

  // Lança NG0950: o inicializador de campo roda na construção,
  // antes de o Angular aplicar o binding [order] do pai
  readonly totalNoInit = this.order().total;

  // Seguro: o computed roda na primeira leitura, com o binding já aplicado
  readonly total = computed(() => this.order().total);

  constructor() {
    // Lança NG0950: o construtor roda antes dos bindings
    console.log(this.order().id);

    // Seguro: o effect roda no change detection, depois do binding
    effect(() => console.log('pedido mudou', this.order().id));
  }

  ngOnInit() {
    // Seguro: os inputs estão garantidos aqui e dali pra frente
    this.carregarHistorico(this.order().id);
  }
}

O conserto que a doc dá, e o que ela não dá

Pra um bug real de timing, os consertos documentados são dois. Leia o input num contexto reativo: o template, um computed ou um effect, que rodam todos depois do binding. Ou leia no ngOnInit ou mais pra frente. Um computed em cima de um input obrigatório é a saída que eu mais uso, porque expressa a derivação uma vez e continua correta conforme o input muda, sem nenhum hook de ciclo de vida pra lembrar.

Tem um terceiro conserto que a página do erro não cita, porque é uma decisão de desenho, não de timing. Se "ainda sem valor" é um estado real em que o seu componente pode estar, o input não devia ser obrigatório. Dê um valor padrão com input<T>(fallback) e tire o .required. Aí um pai sem binding passa a ser válido, a leitura precoce devolve o padrão, e o NG0950 não tem como disparar. Optar por isso é um julgamento: deixe o input obrigatório quando um valor ausente é um erro de programação que você quer pegar no build, e deixe opcional com padrão quando a ausência é um estado que o componente foi feito pra renderizar.

Consertando o binding ausente no pai

Quando a causa é a segunda, o trabalho está no template do pai, não no filho. O Angular já cobra os inputs obrigatórios em tempo de build quando o componente é usado num template, então um binding simplesmente ausente costuma aparecer primeiro como erro de compilação. O NG0950 em runtime tende a significar que o binding existe mas resolve pra algo inútil, ou que o elemento é criado por um caminho que o checador de template não cobre, como uma chamada dinâmica de createComponent.

Num componente criado dinamicamente, o binding não vem de um template, então nada te obriga a passar. Você mesmo tem que setar o input na referência do componente criado, e setar antes de qualquer leitura. A regra é a mesma: o valor precisa estar no lugar antes de o ngOnInit ou a primeira leitura de computed/effect rodar.

Fazendo o binding do input obrigatório num componente criado na mãots
// Caminho via template: o compilador cobra [order]; sem ele, o
// build quebra antes de o NG0950 ter chance de rodar
// <order-summary [order]="selecionado()" />

// Caminho dinâmico: sem template, você seta o input na mão
const ref = this.container.createComponent(OrderSummary);
ref.setInput('order', this.selecionado());
// setInput antes de o change detection ler o valor; senão a
// primeira leitura ainda cai num input obrigatório vazio e lança

Artefato para reaproveitar

Onde é seguro ler um input obrigatório

  • Inicializador de campo ou construtor: nunca leia um input obrigatório aqui; os dois rodam antes do binding e lançam NG0950.
  • Precisa do valor uma vez, no init: leia no ngOnInit ou num hook posterior, onde os inputs estão garantidos.
  • Precisa de um valor derivado dele: use computed(() => input()); a derivação roda depois do binding e acompanha as mudanças.
  • Precisa de um efeito colateral sobre o valor: use effect(() => ...); roda no change detection, depois do binding.
  • A ausência é um estado real pra renderizar: tire o .required e use input<T>(padrão) em vez de mexer no timing do ciclo de vida.
  • O erro continua com a leitura movida: é binding ausente; conserte o template do pai ou use setInput no componente dinâmico.

Perguntas frequentes

O que o NG0950 quer dizer no Angular?

Quer dizer que um signal input obrigatório, declarado com input.required(), foi lido antes de o Angular vincular um valor a ele. Inputs obrigatórios não têm valor inicial, então ler um deles antes do binding do pai não tem nada pra retornar, e o Angular lança o erro em vez de devolver undefined.

Por que ler um input obrigatório no construtor lança NG0950?

O construtor e os inicializadores de campo rodam como parte da construção do componente, antes de o Angular aplicar os bindings do pai. A doc garante os inputs só no ngOnInit e dali pra frente, então uma leitura no construtor acontece com o input obrigatório ainda vazio.

Onde é seguro ler um input obrigatório?

Dentro do template, de um computed ou de um effect, que rodam todos depois do binding, ou dentro do ngOnInit ou de um hook posterior. Um computed em cima do input obrigatório é uma escolha comum porque deriva uma vez e segue correto conforme o input muda.

É só dar um valor padrão pro input?

Só se 'ainda sem valor' for um estado real que o seu componente deva renderizar. Aí use input<T>(padrão) e tire o required. Se um valor ausente é um erro de programação que você quer pegar no build, mantenha obrigatório e mova a leitura pra um ponto seguro.

Fontes consultadas

Modern Angular Playbook

Este artigo é um play.

O Playbook do Angular Moderno reúne o diagnóstico, a matriz de adoção, onze jogadas e o plano de 30 dias. Grátis, em inglês e português.

Você recebe os dois PDFs por email, pela lista do Dojo IA.

Abrir playbook →

André Ramosdisponível para vagas remotas, UTC−3Entre em contato →

Leia também

Nota · 6 min

Como eu adotaria Signal Forms agora que são estáveis no Angular 22

Um filtro de adoção pra Signal Forms agora que são estáveis no Angular 22: use em forms novos, mantenha reactive forms onde já funcionam.

Ler artigo →

Guia · 8 min

OnPush é o default no Angular 22: o que quebra e o que fazer

Por que o Angular 22 faz do OnPush o default, o que de fato quebra, os dois jeitos de corrigir, e como os Eager que a migração adicionou servem de backlog de limpeza.

Ler artigo →

Erro · 9 min

NG0203: por que o inject() falha fora do contexto de injeção

Por que o Angular estoura o NG0203 quando o inject() roda fora do contexto de injeção, a janela exata em que o inject() é válido, as armadilhas comuns, e os fixes ranqueados do field initializer ao runInInjectionContext.

Ler artigo →