Component Communication in Angular: Mastering @Input and @Output

In a modern Angular application, the user interface is built using a tree of components. To make these components dynamic and interactive, they need to talk to each other. This is known as Component Communication. The most common pattern is communication between a Parent component and its Child component using the @Input and @Output decorators.

Understanding the Parent-Child Relationship

Before diving into the code, let's visualize how data flows. In Angular, data flows down from parent to child and events flow up from child to parent.

    [ Parent Component ]
           |
           | (Data Flow Down via @Input)
           v
    [ Child Component ]
           |
           | (Event Flow Up via @Output)
           v
    [ Parent Component ]
    

1. Passing Data Down: The @Input Decorator

The @Input() decorator is used to define a property in a child component that can receive values from its parent. Think of it like a function parameter for a component.

Example: Child Component

In the child component (e.g., user-profile.component.ts), we define a property decorated with @Input().

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

    @Component({
      selector: 'app-user-profile',
      template: '<p>User Name: {{ username }}</p>'
    })
    export class UserProfileComponent {
      @Input() username: string = '';
    }
    

Example: Parent Component

In the parent component's HTML, we use Property Binding to pass data to the child.

    <app-user-profile [username]="'John Doe'"></app-user-profile>
    

2. Sending Data Up: The @Output Decorator

The @Output() decorator allows a child component to raise an event to notify the parent about a change or an action (like a button click). This is used in combination with the EventEmitter class.

Example: Child Component

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

    @Component({
      selector: 'app-child-button',
      template: '<button (click)="notifyParent()">Click Me</button>'
    })
    export class ChildButtonComponent {
      @Output() statusChange = new EventEmitter<string>();

      notifyParent() {
        this.statusChange.emit('Button was clicked in child!');
      }
    }
    

Example: Parent Component

The parent listens for the custom event using Event Binding.

    <app-child-button (statusChange)="handleStatus($event)"></app-child-button>

    // In Parent TypeScript:
    handleStatus(message: string) {
      console.log(message);
    }
    

Real-World Use Case: Shopping Cart

Imagine a ProductListComponent (Parent) and a ProductItemComponent (Child). The parent passes the product details to the child using @Input. When the user clicks "Add to Cart" inside the child component, it notifies the parent via @Output so the total price can be updated in the main dashboard.

Common Mistakes to Avoid

  • Forgetting the Decorator: Beginners often forget to add the @Input() or @Output() parentheses.
  • Wrong Import: Ensure Input, Output, and EventEmitter are imported from @angular/core.
  • Naming Conflicts: Using the same name for an @Input property and a local variable can lead to confusion.
  • Not Using $event: When catching an @Output, the data sent by the child is always contained in the $event variable in the HTML template.

Interview Notes

  • What is @Input? It is a decorator that marks a class field as an input property and supplies configuration metadata. It allows data to flow from parent to child.
  • What is @Output? It is a decorator that marks a class field as an output property and provides an instance of EventEmitter to send data from child to parent.
  • Can we use @Input for complex objects? Yes, you can pass strings, numbers, arrays, or complex JSON objects.
  • What is the difference between Property Binding and @Input? Property binding is the mechanism in the template, while @Input is the decorator in the TypeScript class that enables that binding.

Summary

Component communication is the backbone of Angular applications. Use @Input to push data into a component and @Output with EventEmitter to signal actions back to the parent. For more complex scenarios involving deeply nested components, you might want to look into Services and RxJS (which we will cover in Topic 12) to avoid "Prop Drilling."

In the next lesson, we will explore Component Lifecycle Hooks to understand how to perform actions when these inputs change.