Esta é a lição 11 do Curso de Angular Moderno . Na lição anterior, renderizamos uma lista de produtos usando sinais e `$(‘product’)` @for. Cada card ainda exibe conteúdo de espaço reservado. Agora é hora de conectar dados reais do componente pai ao componente filho usando input()a API de sinais moderna do Angular.
Este post faz parte da série do Curso de Angular Moderno . Confira a página do curso para ver a lista completa de episódios.
Neste post, abordaremos:
- O padrão de comunicação entre pais e filhos
- Criando uma entrada obrigatória com
input.required<T>() - Renderizando dados de entrada no modelo filho.
- Passando dados do pai para o filho com vinculação de propriedade
- Utilizando entradas opcionais com valores padrão.
- Armadilhas comuns a evitar
O Modelo Mental
Antes de escrever qualquer código, vamos alinhar o padrão:
- O componente pai é o proprietário dos dados (a lista de produtos).
- O processo pai percorre os produtos com
@for - O pai/mãe passa um produto para cada cartão infantil.
- A criança recebe dados
input()e os renderiza.
Esta é a comunicação clássica entre pais e filhos, mas utilizando entradas modernas baseadas em sinais. É a primeira forma de comunicação por componentes que abordamos neste curso.
Criando uma entrada obrigatória
Abra product-card.tse adicione uma entrada obrigatória para o produto:
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { Product } from '../product';
@Component({
selector: 'app-product-card',
imports: [MatCardModule, MatButtonModule],
templateUrl: './product-card.html',
styleUrl: './product-card.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCard {
readonly product = input.required<Product>();
}
A frase-chave:
readonly product = input.required<Product>();
Eis o que isso significa:
input.requiredmarca esta entrada como obrigatória<Product>nos dá tipagem rigorosa- A entrada é baseada em sinal, então a lemos com
product()
Se o componente pai se esquecer de passar esse parâmetro, o Angular nos avisará em tempo de compilação e em tempo de execução — que é exatamente o que queremos para dados críticos.
O Decorador @Input() Legado
Se você analisar códigos-fonte ou tutoriais mais antigos do Angular, frequentemente encontrará entradas escritas desta forma:
import { Component, Input } from '@angular/core';
import { Product } from '../product';
export class ProductCard {
@Input({ required: true }) product!: Product;
}
Essa é a abordagem baseada em decoradores que era o padrão antes do Angular v17.
As principais diferenças em comparação com a input()API moderna:
@Input()é um decorador —input()é uma função que retorna um sinal- Com
@Input()`__init__`, o valor é uma propriedade de classe simples; com `__init__`input(), é um sinal que você lê com `__init__`.product() @Input({ required: true })requer uma asserção não nula (!) —input.required()lida com isso de forma limpa, sem sintaxe extra.@Input()não se integra ao sistema de reatividade de sinais do Angular —input()o que faz com que funcione naturalmente comcomputed()eeffect()
Ambas as sintaxes funcionam no Angular atualmente, mas a sintaxe baseada em sinais @Input()está obsoleta e a equipe do Angular indica a intenção de removê-la na versão 22. A sintaxe baseada em sinais input()é a abordagem recomendada atualmente para todo o código novo, e é essa que usaremos ao longo deste curso.
Renderizando os dados de entrada no modelo
Agora vamos substituir o conteúdo de espaço reservado no modelo do cartão por dados reais.
Arquivo:product-card.html
<mat-card class="product-card">
<mat-card-header>
<mat-card-title>{{ product().name }}</mat-card-title>
<mat-card-subtitle>Product</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>{{ product().description }}</p>
<p class="price">{{ product().price | currency }}</p>
</mat-card-content>
<mat-card-actions>
<button matButton>Add to Cart</button>
</mat-card-actions>
</mat-card>
Observe que chamamos `in` product()no modelo. Isso ocorre porque ` input()in` retorna um sinal de entrada, e os sinais são lidos com parênteses — o mesmo padrão que temos usado desde a lição 5.
Transferência de dados do pai para o filho
Agora, conectamos o modelo pai à entrada filha.
Arquivo:products-grid.html
<div class="products-grid">
@for (product of products(); track product.id) {
<app-product-card [product]="product"></app-product-card>
} @empty {
<div class="empty-state">
<h3>No products available</h3>
<p>Check back later for new arrivals!</p>
</div>
}
</div>
O elo que une tudo:
[product]="product"
O lado esquerdo representa o nome do campo de entrada filho. O lado direito representa a variável de loop do elemento pai. Agora, cada ProductCardinstância recebe um objeto de produto da lista.
Entradas opcionais com valores padrão
Os campos obrigatórios são perfeitos para dados indispensáveis, como [exemplo de dado] product. Mas, às vezes, queremos um comportamento configurável que possa recorrer a um valor padrão.
Vamos adicionar um campo de entrada opcional para controlar o texto do rótulo do botão.
Arquivo:product-card.ts
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { Product } from '../product';
@Component({
selector: 'app-product-card',
imports: [MatCardModule, MatButtonModule],
templateUrl: './product-card.html',
styleUrl: './product-card.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCard {
readonly product = input.required<Product>();
readonly addButtonLabel = input('Add to Cart');
}
Aqui, input('Add to Cart')significa:
- Este campo é opcional.
- O valor padrão é
'Add to Cart' - O pai pode sobrescrevê-lo quando necessário.
Agora atualize o modelo para usá-lo:
Arquivo:product-card.html
<mat-card class="product-card">
<mat-card-header>
<mat-card-title>{{ product().name }}</mat-card-title>
<mat-card-subtitle>Product</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>{{ product().description }}</p>
<p class="price">{{ product().price | currency }}</p>
</mat-card-content>
<mat-card-actions>
<button matButton>{{ addButtonLabel() }}</button>
</mat-card-actions>
</mat-card>
E se alguma vez você quiser substituir o texto do botão pelo texto do elemento pai:
<app-product-card [product]="product" [addButtonLabel]="'View Details'"></app-product-card>
Isso torna o componente mais reutilizável sem complexidade adicional.
Armadilhas comuns
Dois erros comuns a evitar:
- Esquecer os parênteses no modelo — escrever
product.nameem vez deproduct().name. Como as entradas são sinais, você deve chamá-las com()para ler o valor. - Marcar dados importantes como opcionais quando deveriam ser obrigatórios — se o componente não funcionar sem um dado, use `
input.required<T>(). Se for uma configuração com um valor padrão adequado, use `input(defaultValue).
Uma boa regra:
- Se o componente não funcionar sem ele →
input.required<T>() - Se for configuração →
input(defaultValue)
Código-fonte
O código-fonte completo do curso está disponível no GitHub: loiane/modern-angular .
Próximo passo
Na próxima lição, vamos aprofundar esse conceito adicionando uma interface de usuário condicional no cartão, usando ` @ifand` @elsepara exibir preços promocionais e selos.
Assista ao vídeo
Este post faz parte da série do Curso de Angular Moderno . Confira a página do curso para ver a lista completa de episódios.




