Modern Angular
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 campo | Não, roda durante a construção | Não, lança NG0950 |
| construtor | Não, roda antes dos bindings | Não, lança NG0950 |
| computed(() => ...) | Sim, a derivação roda na primeira leitura | Sim |
| effect(() => ...) | Sim, roda durante o change detection | Sim |
| ngOnInit e hooks seguintes | Sim, garantido disponível | Sim |
| Expressão no template | Sim, avaliada no change detection | Sim |
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.
@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.
// 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çaArtefato 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
- https://angular.dev/errors/NG0950
- https://angular.dev/api/core/input
- https://angular.dev/guide/components/inputs
- https://angular.dev/guide/signals
- https://angular.dev/guide/components/lifecycle
- https://angular.dev/api/core/computed
- https://angular.dev/api/core/effect
- https://github.com/angular/angular/releases/tag/17.1.0
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.
André Ramosdisponível para vagas remotas, UTC−3Entre em contato →