Sharing Data with Input and Output Decorators
When building applications, components often need to exchange data — either from a parent down to a child, or between components that have no direct relationship.
Angular offers several mechanisms for component communication:
- Using
@Input()and@Output()decorators. - Using the
@ViewChilddecorator. - Using
BehaviorSubjectwith RxJS.
These approaches are useful for scenarios like displaying a product list and then retrieving the selected product name in a separate component when a user clicks an item.
- The list-products.component displays the product list passed down from the parent.
- The app.component displays the currently selected product.
Employing the Input and Output Decorators
The @Input() decorator provides a straightforward way for a child component to receive data from its parent. In list-product.component.ts, declare a productList property using the @Input() decorator.
import { Component, Input, OnInit } from '@angular/core';
export class ProductListComponent implements OnInit {
@Input() productList = [];
ngOnInit() {}
}
Next, modify the template to render the product list using the *ngFor directive.
<li *ngFor="let product of productList">
{{ product.name }}
</li>
Refer to the official documentation for the *ngFor directive for more details.
On the parent side, app.component.ts defines a products variable containing the data to display.
export class AppComponent {
products = [
{ name: 'Rice', id: 1, price: 200 },
{ name: 'Beans', id: 2, price: 300 },
{ name: 'Bananna', id: 3, price: 400 },
];
Modify app.component.html to pass the data down to the child component using the [productList] property binding.
<app-product-list
class="card p-2"
[productList]="products"
></app-product-list>
See the Angular guide on property binding for further reading.
Now that data flows from parent to child via the Input() decorator, the next task is capturing the user's selection in the child component and having the parent react to it.
Retrieving the Selected Product from the Child
By pairing the @Output() decorator with an EventEmitter, the child can notify the parent about changes. In product-list.component.ts, declare an onSelected property using the @Output() decorator and assign it a new EventEmitter instance.
Add a method called onSelectedProduct that accepts a product and invokes the onSelected emitter, passing that product along.
For more on this, see the EventEmitter API reference.
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css'],
})
export class ProductListComponent implements OnInit {
@Input() productList = [];
@Output() onSelected = new EventEmitter<any>();
constructor() {}
ngOnInit() {}
onSelectedProduct(product) {
console.log(product);
this.onSelected.emit(product);
}
}
In product-list.component.html, attach a click event listener to each product item and invoke the onSelectedProduct method within it.
<li *ngFor="let product of productList" (click)="onSelectedProduct(product)">
{{ product.name }}
</li>
Now update app.component.ts by adding a method that listens for the onSelected event and sets it to the component's selectedProduct property.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
selectedProduct: any;
products = [
{ name: 'Rice', id: 1, price: 200 },
{ name: 'Beans', id: 2, price: 300 },
{ name: 'Bananna', id: 3, price: 400 },
];
onSelectedProduct(product) {
this.selectedProduct = product;
}
}
Finally, adjust app.component.html to subscribe to the (onSelected) event. Connect it to the handler method and pass the $event object. Display the chosen product using an *ngIf guard on the selectedProduct property.
<app-product-list
class="card p-2"
[productList]="products"
(onSelected)="onSelectedProduct($event)"
></app-product-list>
<div *ngIf="selectedProduct" class="card">
<h1>You selected {{ selectedProduct.name }}</h1>
</div>
Check out the Angular guide on event binding for more examples.
Accessing Child Components with ViewChild
There are occasions when we need to reach into a child component and work with its properties or invoke its methods. The ViewChild decorator makes this possible by letting us inject one component into another.
Keep in mind that the injected component is only available once the
AfterViewInitlifecycle hook has fired.
Start by adding a sessionId property to product-list.component.ts and initialize it with Math.random().
export class ProductListComponent implements OnInit {
sessionId = Math.random();
For more detail, check the official ViewChild documentation.
In app.component.ts, declare a property called sessionId and apply the ViewChild decorator to it, passing in ProductListComponent as the selector.
export class AppComponent implements AfterViewInit {
@ViewChild(ProductListComponent) productList;
sessionId: any;
Then implement the AfterViewInit interface and copy the sessionId value from ProductListComponent into the corresponding property in the app component.
ngAfterViewInit() {
this.sessionId = this.productList.sessionId;
}
Learn more about the AfterViewInit lifecycle hook.
Display sessionId in app.component.html.
<h1>The session id is {{ sessionId }}</h1>
That's all there is to it — you now have direct access to the properties and methods of the ProductList component.
Leveraging a Service with a Behavior Subject
The techniques above are reliable and production-ready. However, they establish a direct connection between a parent and child component, which becomes awkward when the component tree grows beyond a few levels.
An alternative that scales better is a shared service that keeps the state synchronized. By combining RxJS with a BehaviorSubject, we get a clean channel for component communication with several advantages:
- No stale data concerns — unlike
ViewChild, which only captures a snapshot during the AfterView lifecycle and may require additional work likeDetectChangesto stay current, the service always pushes the latest value. - Every component subscribed to the service automatically receives updates.
- There is no need for a parent-child relationship; any component can participate regardless of its position in the tree.
First, generate a service called product-service. Inside it, define a product$ field as a behavior subject that holds the current product, and expose a public observable selectedProduct derived from it.
Add a setProduct method that takes a new product and pushes it into the subject.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class ProductService {
private product$ = new BehaviorSubject<any>({});
selectedProduct$ = this.product$.asObservable();
constructor() {}
setProduct(product: any) {
this.product$.next(product);
}
}
Here is a deeper look at behavior subjects.
Next, inject the product service into the app component, subscribe to the selectedProduct observable, and store each emitted value in the component's own selectedProduct field.
constructor(private productService: ProductService) {}
ngOnInit(): void {
this.productService.selectedProduct$.subscribe((value) => {
this.selectedProduct = value;
});
}
Now inject the product service into the product-list component as well and modify the onSelected handler so it delegates to the service's setProduct method.
constructor(private productService: ProductService) {}
onSelectedProduct(product) {
this.productService.setProduct(product);
}

With this in place, the components exchange data freely without any direct coupling between them.
Rework the product-list component
We can streamline the code by shifting more responsibilities to the service layer.
- Set up a behavior subject and associated methods for managing the product list.
- Have the product-list component subscribe to the service to retrieve the product data.
Update the product-service by adding two new fields for the productList and a method to push the product list to subscribers.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class ProductService {
private product$ = new BehaviorSubject<any>({});
selectedProduct$ = this.product$.asObservable();
private productListBus$ = new BehaviorSubject<any>([]);
productList$ = this.productListBus$.asObservable();
constructor() {}
setProduct(product: any) {
this.product$.next(product);
}
setProductList(products: any) {
this.productListBus$.next(products);
}
}
The app.component update
Inject the product service into the constructor. Within the ngOnInit lifecycle hook, subscribe to the setProductList method exposed by the service.
import { OnInit, Component, ViewChild, AfterViewInit } from '@angular/core';
import { ProductListComponent } from './product-list/product-list.component';
import { ProductService } from './product-service.service';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, AfterViewInit {
@ViewChild(ProductListComponent) productList;
sessionId: any;
selectedProduct: any;
products = [
{ name: 'Rice', id: 1, price: 200 },
{ name: 'Beans', id: 2, price: 300 },
{ name: 'Bananna', id: 3, price: 400 },
];
constructor(private productService: ProductService) {}
ngOnInit(): void {
this.productService.selectedProduct$.subscribe((value) => {
this.selectedProduct = value;
});
this.productService.setProductList(this.products);
}
ngAfterViewInit(): void {
this.sessionId = this.productList.sessionId;
}
}
The (onSelected) event binding can now be removed from the template.
<app-product-list class="card p-2"></app-product-list>
ProductList component adjustments
Following the same pattern as the app.component, inject the product service into the constructor. In the ngOnInit lifecycle hook, subscribe to the productList observable and assign the retrieved value to the component's productList property.
Finally, strip out the Input and output properties from productlist.component.ts.
import { Component, Input, OnInit, Output } from '@angular/core';
import { ProductService } from '../product-service.service';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css'],
})
export class ProductListComponent implements OnInit {
productList = [];
sessionId = Math.random();
constructor(private productService: ProductService) {}
ngOnInit() {
this.productService.productList$.subscribe((value) => {
this.productList = value;
});
}
onSelectedProduct(product) {
this.productService.setProduct(product);
}
}
With that, the components now communicate cleanly without relying on Input and Output decorators.
Wrapping up
This series covered three distinct approaches for handling inter-component communication in Angular. For simple, direct relationships—such as a parent passing data down to a child—the @Input and @Output decorators are perfectly adequate.
If your data needs to be accessed by multiple components across different parts of the application, routing the communication through a service backed by a behavior subject is the recommended strategy.
For further exploration, the official Angular documentation is an excellent resource.

