Angular course · Module 2: Components

Input and Output - communication between components

7 min read ·
In this lesson6

A new component has appeared in the dojo: the warrior card app-samurai-card. It is supposed to show a samurai's name and level, but how is it to know which samurai? It cannot guess on its own, because the list of students is known to the enclosing component, the parent. The same trouble exists the other way round: when the learner clicks the attack button on a card, the list has to find out who attacked, so it can add that to the battle log.

Angular solves this with one rule: data flows down, events flow up. The parent gives the child orders through @Input, and the child sends reports to the parent through @Output. Just like in the shogun's army: a general does not look inside a soldier's head, and a soldier does not give orders to a general. Both sides communicate through agreed channels.

@Input - orders from the shogun

The @Input() decorator marks a field whose value the parent can provide. The field gets a default value, which the component uses when the parent passes nothing:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-samurai-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ name }}</h3>
      <p>Level: {{ level }}</p>
    </div>
  `
})
export class SamuraiCardComponent {
  @Input() name = '';
  @Input() level = 1;
}

You import the Input decorator from @angular/core, just like Component. The card does not know where the data will come from, and it does not need to: it simply displays what it gets. The parent passes the values as attributes in the component's tag:

<app-samurai-card name="Musashi" [level]="10"></app-samurai-card>
<app-samurai-card [name]="bestStudent" [level]="bestLevel"></app-samurai-card>

name="Musashi" passes a fixed text. [level]="10" is the property binding from the second lesson: Angular evaluates the expression 10 and passes a number. Without the brackets, as level="10", the child would get the string "10". In the second line, [name]="bestStudent" reads the bestStudent field from the parent's class, and when the parent changes it, the card shows the new name by itself. For text, both notations are equivalent: [name]="'Musashi'" gives the same result as name="Musashi". Remember too that the parent must have SamuraiCardComponent on its imports list.

A required input

Sometimes a default value makes no sense. A card without a clan is useless, so we want the parent to be unable to forget it. That is what the required option in the decorator is for:

export class SamuraiCardComponent {
  @Input() name = '';
  @Input() level = 1;
  @Input({ required: true }) clan!: string;
}

If the parent places the card without the clan attribute, the Angular compiler reports an error before the application even starts. The exclamation mark in clan!: string is for TypeScript: it is a promise that the field will get a value, even though the declaration does not give it one. Without it, strict mode, which Angular turns on by default, would report that the field has no initial value. Here the promise is safe, because required guarantees that the parent provides the value.

@Output - a report for the shogun

Now the opposite direction. The child announces an event through a field marked with the @Output() decorator. The value of the field is an EventEmitter, an event transmitter, and in angle brackets you give the type of the value the event carries:

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-attack-button',
  standalone: true,
  template: `<button (click)="onAttack()">Attack!</button>`
})
export class AttackButtonComponent {
  @Output() attacked = new EventEmitter<number>();

  onAttack(): void {
    const damage = Math.floor(Math.random() * 100);
    this.attacked.emit(damage);
  }
}

new EventEmitter<number>() creates a transmitter of events that carry a number. <number> is a type parameter: TypeScript makes sure nobody sends a string or an object through attacked. When the button is clicked, the onAttack() method draws a random damage value from 0 to 99 and sends it to the parent with the call this.attacked.emit(damage). emit on its own displays nothing. It is only a report, and the parent decides what to do with it.

The parent listens to the child's event just like to a click, with an event binding that uses the name of the field marked with @Output:

import { Component } from '@angular/core';
import { AttackButtonComponent } from './attack-button.component';

@Component({
  selector: 'app-battle',
  standalone: true,
  imports: [AttackButtonComponent],
  template: `
    <app-attack-button (attacked)="handleAttack($event)"></app-attack-button>
    <p>Last damage: {{ lastDamage }}</p>
  `
})
export class BattleComponent {
  lastDamage = 0;

  handleAttack(damage: number): void {
    this.lastDamage = damage;
    console.log(`Damage dealt: ${damage}`);
  }
}

With an event from @Output, the $event variable is not a browser event but exactly the value the child passed to emit(). Here it is the damage number, so handleAttack stores it in lastDamage right away, and the interpolation shows it on the screen. The child knows nothing about lastDamage, and the parent does not know how the child calculates the damage. Each side looks after its own part.

The whole flow step by step

Let us follow the report's journey from the child to the parent once more. Each step follows from the previous one:

  1. The child declares the field @Output() attacked = new EventEmitter<number>().
  2. An event happens in the child, for example the learner clicks the attack button.
  3. The child calls this.attacked.emit(damage) and sends the value.
  4. The parent receives it through (attacked)="handleAttack($event)", where $event is the value that was sent.

An order going down through @Input is simpler, because it needs no event at all: the parent changes the value in its field, and Angular passes it to the child on the next view refresh.

The newer API: the input() and output() functions

Since Angular 19, the input() and output() functions are also stable, and they do the same job without decorators. This is what a warrior card written in this style looks like:

import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-warrior-card',
  standalone: true,
  template: `
    <h3>{{ name() }}</h3>
    <button (click)="attacked.emit(name())">Attack!</button>
  `
})
export class WarriorCardComponent {
  name = input('Unknown');
  attacked = output<string>();
}

The call input('Unknown') creates an input with a default value. You read its value with parentheses, name(), because it is a signal, a value that notifies Angular about its changes by itself. You will study signals in depth in the sixth module. A required input is written as input.required<string>(). In turn, output<string>() replaces @Output() with an EventEmitter and has the same emit() method. The parent sees no difference at all: it still writes [name]="..." and (attacked)="...". In this module's tasks you can use either notation, but it is worth knowing the decorators, because you will meet them in most existing projects.

Full example - a list with selection

Let us combine both directions. The samurai card from the first example gets a button and a selected event, and the list displays the cards in a @for loop and remembers the chosen warrior:

// The samurai card from the first example, extended with @Output
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-samurai-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ name }}</h3>
      <p>Level: {{ level }}</p>
      <button (click)="selected.emit(name)">Select</button>
    </div>
  `
})
export class SamuraiCardComponent {
  @Input() name = '';
  @Input() level = 1;
  @Input({ required: true }) clan!: string;
  @Output() selected = new EventEmitter<string>();
}

// Samurai list with selection capability
@Component({
  selector: 'app-samurai-list',
  standalone: true,
  imports: [SamuraiCardComponent],
  template: `
    @for (samurai of samuraiList; track samurai.id) {
      <app-samurai-card
        [name]="samurai.name"
        [level]="samurai.level"
        [clan]="samurai.clan"
        (selected)="onSelect($event)">
      </app-samurai-card>
    }

    @if (selectedName) {
      <p>Selected: {{ selectedName }}</p>
    }
  `
})
export class SamuraiListComponent {
  samuraiList = [
    { id: 1, name: 'Musashi', level: 10, clan: 'Niten' },
    { id: 2, name: 'Kojiro', level: 9, clan: 'Ganryū' }
  ];

  selectedName = '';

  onSelect(name: string): void {
    this.selectedName = name;
  }
}

The list passes three inputs to each card: name, level and the required clan. The card displays the name and the level, and when its button is clicked it sends its name with the call selected.emit(name) straight from the template. The list receives it through (selected)="onSelect($event)", stores it in selectedName, and the @if block shows the paragraph "Selected: Musashi". You have met every element of this example in this module: the decorator, the bindings, control flow and communication between components.

Remember: orders flow down through @Input, reports come back up through @Output and emit(), and no warrior reaches into another's class without an agreed channel.

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Which decorator is used to send events from a child component to its parent?

  2. 2. Which decorator lets a child component receive data from its parent component?

  3. 3. How do you mark an @Input as required, according to the lesson?

  4. 4. Which syntax correctly represents two-way binding in Angular?

Hands-on tasks in the game

  • Click in order

    Arrange the tokens to create an event object: new EventEmitter<number>()

  • Code editor

    Complete the @Input decorator for the warrior's name and the @Output decorator for the attack event.

  • Horizontal ordering

    Arrange the single-line tokens to emit an event with the damage: this.attacked.emit(damage);

  • Click in order

    Arrange the tokens to pass the warrior's level to the child with property binding: [level]="10"

  • Vertical ordering

    Arrange the steps of the data flow in parent-child communication via @Output (top to bottom):

  • Vertical ordering

    Arrange the main parts of a component file in their typical order (top to bottom):

Useful articles