Angular Component Inputs: Pass Data to Child Components

Angular Tutorial: Beginner to Advanced · 6. Reusable components · 3 min read · Updated

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.required for anything the component cannot work without.
  • Do not change an object you received through an input. Treat inputs as read-only.

Try it yourself

  1. Add an optional badge input to the card and show it only when it is set, using @if.
  2. Add a third course to the parent and see the new card appear.
  3. Remove [lessons] from one usage and read the compile error.

Quick check

  1. What is the difference between input() and input.required()?
  2. How does a parent bind a value to an input?
  3. 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.

Frequently asked questions

Do I need the @Input decorator?

Older code uses @Input() on a property. It still works, but the input() function is the current style, it gives you signals and works nicely with computed().

Can an input have a different name from its property?

Yes. Pass an options object with alias, for example input(0, { alias: 'count' }). Use it sparingly, since matching names are easier to follow.