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)
import { Component } from '@angular/core';
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)
import { Component, Input } from '@angular/core';
selector: ‘app-child’,
template: `
<p>{{ message }}</p>
`,
})
export class ChildComponent {
() 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)
import { Component } from '@angular/core';
selector: ‘app-root’,
template: `
<app-child [person]=”parentPerson”></app-child>
`,
})
export class AppComponent {
parentPerson = { firstName: ‘John’, lastName: ‘Doe’ };
}
Child Component (child.component.ts)
import { Component, Input } from '@angular/core';
selector: ‘app-child’,
template: `
<p>{{ person.firstName }} {{ person.lastName }}</p>
`,
})
export class ChildComponent {
() 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)
import { Component, Output, EventEmitter } from '@angular/core';
selector: ‘app-child’,
template: `
<button (click)=”sendMessage()”>Send Message</button>
`,
})
export class ChildComponent {
() 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)
import { Component } from '@angular/core';
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)
import { Component, Output, EventEmitter } from '@angular/core';
selector: ‘app-child’,
template: `
<button (click)=”sendMessage()”>Send Message</button>
`,
})
export class ChildComponent {
() 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)
import { Component } from '@angular/core';
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:
- A grandparent component has some data that it wants to pass to a parent component.
- The parent component further processes the data and wants to pass a modified version of it to a child component.
- 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)
import { Component } from '@angular/core';
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)
import { Component, Input, Output, EventEmitter } from '@angular/core';
selector: ‘app-parent’,
template: `
<app-child [parentData]=”grandparentData” (childResult)=”handleChildResult($event)”></app-child>
`,
})
export class ParentComponent {
() grandparentData: string;
() result = new EventEmitter<string>();
handleChildResult(childResult: string) {
const modifiedResult = `Modified: ${childResult}`;
this.result.emit(modifiedResult);
}
}
Child Component (child.component.ts)
import { Component, Input, Output, EventEmitter } from '@angular/core';
selector: ‘app-child’,
template: `
<button (click)=”generateResult()”>Generate Result</button>
`,
})
export class ChildComponent {
() parentData: string;
() 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.