Introduction

Angular is a popular front-end framework known for its ability to build dynamic and interactive web applications. One of its key features is the component-based architecture, which promotes the reusability of code and the separation of concerns. In this article, we will dive deep into two important decorators in Angular, @Input and @Output, and explore how they can be used to create communication channels between components.

Understanding Component Communication

In Angular applications, components are the building blocks of the user interface. They encapsulate both the presentation and behavior of a part of the UI. Often, you’ll find the need for components to communicate with each other, whether it’s passing data from a parent component to a child component or emitting events from a child component to a parent component.

This communication can be achieved using the @Input and @Output decorators, which facilitate the exchange of data between components in a clean and predictable manner.

The @Input Decorator

The @Input decorator allows a parent component to pass data to a child component. It is used to bind a property of a child component to a property of a parent component. Let’s look at a simple example to illustrate this concept:

Parent Component (app.component.ts)

typescript

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

@Component({
selector: ‘app-root’,
template: `
<app-child [message]=”parentMessage”></app-child>
`
,
})
export class AppComponent {
parentMessage = ‘Hello from Parent!’;
}

In this example, the AppComponent has a property called parentMessage, which we want to pass to the ChildComponent.

Child Component (child.component.ts)

typescript

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

@Component({
selector: ‘app-child’,
template: `
<p>{{ message }}</p>
`
,
})
export class ChildComponent {
@Input() message: string;
}

The ChildComponent receives the message property using the @Input decorator. This establishes a binding between the message property in the parent component and the message property in the child component. Any changes made to the parentMessage property in the parent component will be reflected in the child component automatically.

Using @Input with Objects and Complex Data

@Input is not limited to simple data types like strings or numbers; it can also be used with objects and complex data structures. Let’s see how to pass an object from a parent component to a child component:

Parent Component (app.component.ts)

typescript

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

@Component({
selector: ‘app-root’,
template: `
<app-child [person]=”parentPerson”></app-child>
`
,
})
export class AppComponent {
parentPerson = { firstName: ‘John’, lastName: ‘Doe’ };
}

Child Component (child.component.ts)

typescript

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

@Component({
selector: ‘app-child’,
template: `
<p>{{ person.firstName }} {{ person.lastName }}</p>
`
,
})
export class ChildComponent {
@Input() person: { firstName: string; lastName: string };
}

In this example, the parent component passes an object called parentPerson to the child component. The child component then displays the first name and last name from the person object.

The @Output Decorator

While @Input is used for passing data from parent to child components, the @Output decorator is used for emitting events from child to parent components. It allows a child component to notify its parent component when something happens.

Let’s look at an example where a child component emits an event when a button is clicked:

Child Component (child.component.ts)

typescript

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

@Component({
selector: ‘app-child’,
template: `
<button (click)=”sendMessage()”>Send Message</button>
`
,
})
export class ChildComponent {
@Output() messageSent = new EventEmitter<void>();

sendMessage() {
this.messageSent.emit();
}
}

In this example, the ChildComponent has an @Output property called messageSent, which is an instance of EventEmitter. When the sendMessage method is called, it emits the messageSent event.

Parent Component (app.component.ts)

typescript

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

@Component({
selector: ‘app-root’,
template: `
<app-child (messageSent)=”handleMessageSent()”></app-child>
<p *ngIf=”messageReceived”>Message received from child!</p>
`
,
})
export class AppComponent {
messageReceived = false;

handleMessageSent() {
this.messageReceived = true;
}
}

In the parent component, we use the (messageSent) binding to listen for the messageSent event emitted by the child component. When the event is triggered, the handleMessageSent method is called, and we update the messageReceived property to true, which displays a message in the template.

Passing Data with @Output

The @Output decorator can also be used to pass data from a child component to a parent component along with emitting events. Let’s see how to pass a message from a child component to its parent:

Child Component (child.component.ts)

typescript

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

@Component({
selector: ‘app-child’,
template: `
<button (click)=”sendMessage()”>Send Message</button>
`
,
})
export class ChildComponent {
@Output() messageSent = new EventEmitter<string>();

sendMessage() {
const message = ‘Hello from Child!’;
this.messageSent.emit(message);
}
}

In this updated example, the sendMessage method not only emits the messageSent event but also includes a message as its payload.

Parent Component (app.component.ts)

typescript

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

@Component({
selector: ‘app-root’,
template: `
<app-child (messageSent)=”handleMessageSent($event)”></app-child>
<p *ngIf=”receivedMessage”>{{ receivedMessage }}</p>
`
,
})
export class AppComponent {
receivedMessage = ;

handleMessageSent(message: string) {
this.receivedMessage = message;
}
}

In the parent component, we now pass the $event object to the handleMessageSent method, which captures the message sent by the child component. This message is then displayed in the template.

@Input and @Output Chains

In more complex Angular applications, you may encounter situations where you need to create chains of @Input and @Output properties to pass data between multiple components. This allows you to establish communication channels between different levels of your component hierarchy.

Consider the following scenario:

  1. A grandparent component has some data that it wants to pass to a parent component.
  2. The parent component further processes the data and wants to pass a modified version of it to a child component.
  3. The child component performs some action and wants to notify the grandparent component about the result.

Let’s see how we can achieve this using @Input and @Output chains:

Grandparent Component (grandparent.component.ts)

typescript

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

@Component({
selector: ‘app-grandparent’,
template: `
<app-parent [grandparentData]=”grandparentData” (result)=”handleResult($event)”></app-parent>
<p *ngIf=”receivedResult”>Received result: {{ receivedResult }}</p>
`
,
})
export class GrandparentComponent {
grandparentData = ‘Data from Grandparent’;
receivedResult = ;

handleResult(result: string) {
this.receivedResult = result;
}
}

Parent Component (parent.component.ts)

typescript

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

@Component({
selector: ‘app-parent’,
template: `
<app-child [parentData]=”grandparentData” (childResult)=”handleChildResult($event)”></app-child>
`
,
})
export class ParentComponent {
@Input() grandparentData: string;
@Output() result = new EventEmitter<string>();

handleChildResult(childResult: string) {
const modifiedResult = `Modified: ${childResult}`;
this.result.emit(modifiedResult);
}
}

Child Component (child.component.ts)

typescript

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

@Component({
selector: ‘app-child’,
template: `
<button (click)=”generateResult()”>Generate Result</button>
`
,
})
export class ChildComponent {
@Input() parentData: string;
@Output() childResult = new EventEmitter<string>();

generateResult() {
const result = `Result from Child using: ${this.parentData}`;
this.childResult.emit(result);
}
}

In this example, the grandparent component passes data to the parent component using the grandparentData input property. The parent component further processes the data and emits a result using the result output property. The child component receives the data from the parent using the parentData input property and, when a button is clicked, emits a result using the childResult output property. Finally, the grandparent component listens to the result event from the parent component and displays the received result.

Conclusion

Understanding and mastering the use of @Input and @Output decorators in Angular is essential for building complex and interactive web applications. These decorators provide a powerful mechanism for establishing communication between components, allowing data to flow seamlessly through your application’s component hierarchy.

By using @Input, you can pass data from parent to child components, including complex objects and data structures. On the other hand, @Output allows child components to emit events and pass data back to their parent components. By combining these decorators, you can create sophisticated communication patterns to meet the requirements of your Angular applications.

As you continue to develop Angular applications, you’ll find that component communication is a fundamental aspect of building responsive and interactive user interfaces. With the knowledge gained from this article, you’ll be well-equipped to leverage the full potential of @Input and @Output decorators in your Angular projects, enabling you to create dynamic and data-rich web applications with ease.