So far each component has held its own data. Real apps are built from reusable components that are given their data from outside, like a product card that can show any product. In Angular, a parent component passes data to a child through inputs.
What you will learn
- How to declare an input with
input() - How to require an input or give it a default
- How a parent supplies values with property binding
Declare an input
An input is a property created with the input() function. The child reads it like any signal:
// src/app/course-card/course-card.ts
import { Component, computed, input } from '@angular/core';
@Component({
selector: 'app-course-card',
template: `
<article class="card">
<h3>{{ title() }}</h3>
<p>{{ lessons() }} lessons · {{ level() }}</p>
<p>{{ summary() }}</p>
</article>
`,
styles: `
.card { border: 1px solid #cbd5e1; border-radius: 8px; padding: 12px; margin: 8px 0; max-width: 360px; }
`,
})
export class CourseCard {
// required: the parent must provide it
readonly title = input.required<string>();
readonly lessons = input.required<number>();
// optional, with a default
readonly level = input('Beginner');
readonly summary = computed(() =>
this.lessons() > 20 ? 'A long, in-depth course.' : 'A short course you can finish quickly.',
);
}
There are two kinds of input:
input.required<T>()has no default. The compiler reports an error if a parent forgets it.input(defaultValue)is optional and falls back to the default.
Inputs are read-only signals. The child never changes them. Because they are signals, you can use them inside computed(), as summary does, and the result updates when the parent changes the value.
Provide values from the parent
The parent uses the child's selector and binds to the inputs with square brackets:
// src/app/app.ts
import { Component, signal } from '@angular/core';
import { CourseCard } from './course-card/course-card';
interface CourseInfo {
id: number;
title: string;
lessons: number;
level?: string;
}
@Component({
imports: [CourseCard],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {
protected readonly courses = signal<CourseInfo[]>([
{ id: 1, title: 'Angular for beginners', lessons: 35 },
{ id: 2, title: 'TypeScript essentials', lessons: 12, level: 'Intermediate' },
]);
}
<!-- src/app/app.html -->
<main>
<h1>Courses</h1>
@for (course of courses(); track course.id) {
<app-course-card [title]="course.title" [lessons]="course.lessons" [level]="course.level ?? 'Beginner'" />
}
</main>
The ?? operator means "use the value on the left, unless it is null or undefined, in which case use the right side". Each card is a separate instance with its own inputs, which is exactly the reuse we want.
Plain text inputs
If the value is a fixed string, you can skip the brackets: <app-course-card title="Fixed title" [lessons]="10" />. Use brackets whenever the value is an expression.
Tips for good inputs
- Keep inputs small and simple. Pass the data a component needs, not the whole app state.
- Use
input.requiredfor anything the component cannot work without. - Do not change an object you received through an input. Treat inputs as read-only.
Try it yourself
- Add an optional
badgeinput to the card and show it only when it is set, using@if. - Add a third course to the parent and see the new card appear.
- Remove
[lessons]from one usage and read the compile error.
Quick check
- What is the difference between
input()andinput.required()? - How does a parent bind a value to an input?
- Can a child component change the value of its input?
Data now flows down. Learn how events flow up in Component outputs. Reference: the inputs guide.