Angular, a robust and widely-used web framework, empowers developers to build dynamic and scalable applications. One of its powerful features is the ability to create modular and reusable components, and these components often need to communicate with each other. Angular facilitates this communication through the use of @Input
and @Output
decorator, enabling parent-child interactions seamlessly. In this blog post, we’ll delve into the intricacies of this decorator, providing examples and insights to help you master Angular’s component communication.
If you want to learn one more angular feature, you can refer here.
Understanding Parent-Child Component Relationship
Before we delve into the details of @Input
and @Output
decorators, let’s understand the parent-child component relationship in Angular. In a typical scenario, you may have a parent component that encapsulates one or more child components. Communication between these components can be crucial for passing data from the parent to the child or vice versa.
@Input Decorator
The @Input
decorator allows a parent component to pass data to a child component. It essentially binds a property of the child component to a property of the parent component. Here’s an example to illustrate how it works:
Parent Component
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child [message]="parentMessage"></app-child>
`,
})
export class ParentComponent {
parentMessage = 'Message from Parent';
}
Child Component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h2>Child Component</h2>
<p>{{ message }}</p>
`,
})
export class ChildComponent {
@Input() message: string = '';
}
In this example, the ParentComponent
passes the parentMessage
to the ChildComponent
using the [message]
binding. The @Input
decorator in the child component’s message
property makes this communication possible.
@Output Decorator
Conversely, the @Output
decorator enables communication from a child component to a parent component. It emits custom events that the parent component can listen to. Let’s illustrate this with an example:
Child Component
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message to Parent</button>
`,
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
sendMessage() {
this.messageEvent.emit('Message from Child');
}
}
Parent Component
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child (messageEvent)="receiveMessage($event)"></app-child>
<p>{{ receivedMessage }}</p>
`,
})
export class ParentComponent {
receivedMessage = '';
receiveMessage(message: string) {
this.receivedMessage = message;
}
}
In this example, the ChildComponent
emits a custom event using the messageEvent
property decorated with @Output
. The parent component then listens to this event and handles it with the receiveMessage
method.
Now, let’s dive deeper into Angular’s @Input
and @Output
decorators, exploring additional concepts, best practices, and advanced use cases.
Advanced Usage of @Input
1. Input Alias
You can use the @Input
decorator to set an alias for the property, providing a more expressive and meaningful name in the child component. Consider the following example:
Parent Component
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child [externalMessage]="parentMessage"></app-child>
`,
})
export class ParentComponent {
parentMessage = 'Message from Parent';
}
Child Component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h2>Child Component</h2>
<p>{{ internalMessage }}</p>
`,
})
export class ChildComponent {
@Input('externalMessage') internalMessage: string = '';
}
In this example, the parent component binds parentMessage
to the child component using [externalMessage]
. However, within the child component, the property is named internalMessage
.
2. Using Setter with @Input
You can use a setter method with @Input
to perform additional logic when the input property changes. This is useful when you need to react to changes and take some action.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h2>Child Component</h2>
<p>{{ internalMessage }}</p>
`,
})
export class ChildComponent implements OnChanges {
private _internalMessage: string = '';
@Input()
set internalMessage(value: string) {
// Additional logic, if needed
this._internalMessage = value.toUpperCase();
}
get internalMessage(): string {
return this._internalMessage;
}
ngOnChanges(changes: SimpleChanges): void {
// Handle changes if necessary
}
}
In this example, the internalMessage
property is set using the setter, allowing for custom logic (in this case, converting the message to uppercase).
Advanced Usage of @Output
1. EventEmitter with Custom Types
@Output
can emit custom events with specific data types. This adds type safety to your events. Consider the following example:
Child Component
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message to Parent</button>
`,
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
sendMessage() {
this.messageEvent.emit('Message from Child');
}
}
Parent Component
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child (messageEvent)="receiveMessage($event)"></app-child>
<p>{{ receivedMessage }}</p>
`,
})
export class ParentComponent {
receivedMessage = '';
receiveMessage(message: string) {
this.receivedMessage = message;
}
}
In this example, the messageEvent
emits a string, making the event more explicit and readable.
2. EventEmitter with @ViewChild
You can use @ViewChild
to get a reference to the child component in the parent component and subscribe to its events directly.
Parent Component
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
<h1>Parent Component</h1>
<app-child></app-child>
<p>{{ receivedMessage }}</p>
`,
})
export class ParentComponent implements AfterViewInit {
@ViewChild(ChildComponent) childComponent!: ChildComponent;
receivedMessage = '';
ngAfterViewInit(): void {
this.childComponent.messageEvent.subscribe((message: string) => {
this.receivedMessage = message;
});
}
}
In this example, @ViewChild
is used to get a reference to the ChildComponent
, and the parent component subscribes directly to the messageEvent
emitted by the child.
Conclusion
Understanding the advanced features and best practices of @Input
and @Output
in Angular allows you to build more flexible and maintainable components. Whether it’s using aliases, setters, or advanced event handling, these techniques empower you to create sophisticated and responsive Angular applications. As you continue to explore and apply these concepts, you’ll find that Angular’s component-based architecture becomes an even more powerful tool in your development arsenal. Happy coding!
Finally, for more such posts, please follow our LinkedIn page- FrontEnd Competency.