
This article explores three distinct approaches to facilitating communication between a parent component and a dialog component in Angular, using a single interactive example centered on opening and closing a dialog.
Alain Chautard
September 4, 2025
Originally published on medium.com
I get asked a lot about best practices in terms of Angular architecture. As a result, in this post, I want to showcase three different ways to achieve the same kind of component communication in Angular, highlighting the pros and cons of each approach.
The example we’ll be working on is the following, where two components (a main component and a dialog component) communicate to open and close a dialog:

In this first attempt, my dialog component is entirely driven by inputs and outputs. Here is the TypeScript code of that component:
export class DialogComponent {
isOpen = input(false);
title = input("Title");
onClose = output<string>();
closePopup(): void {
this.onClose.emit('Pop-up window closed');
}
}
In this example, the parent component has to set isOpen to true to open the dialog. Then, the parent component has to revert that value to false when the dialog gets closed:
<!-- Button to open the dialog -->
<button (click)="showDialog = true">Open dialog</button>
<app-dialog title="Test dialog" [isOpen]="showDialog"
(onClose)="showDialog = false">
This dialog is great
</app-dialog>
What’s good about that approach is that our dialog is fully reusable and controllable by its parent. It’s a perfect presentation component, which means we can use the optimized onPush change detection strategy on it.
The downside is that the API isn’t obvious, and having to call (onClose)=”showDialog = false” doesn’t seem natural or straightforward. Why can’t the dialog close itself, right?
You can see the code for that first approach here on Stackblitz.
In this second approach, we want to simplify the API to control the dialog, so we update our component as follows:
export class DialogComponent {
isOpen = signal(false);
title = input("Title");
closePopup(): void {
this.isOpen.set(false);
}
}
What we’ve done here is remove the input/output mechanism that decides when to show/hide the dialog. How do we control the dialog from the parent component, then?
We do so with viewChild, which gives us a reference to the DialogComponent, allowing us to use all public methods and attributes from that component.
TypeScript:
export class App {
dialog = viewChild.required(DialogComponent);
}
And in the HTML template:
<!-- We control the Signal within the dialog component directly -->
<button (click)="dialog().isOpen.set(true)">Open dialog</button>
<app-dialog title="Test dialog">
This dialog is great
</app-dialog>
This works great, the only downside is that the parent component has to know exactly how the dialog component works, and that there is a signal in there to control the visibility of the dialog window.
As a result, this creates a tighter coupling between the two components.
You can see that example in action on Stackblitz here.
Signal-based components brought us the model() function, which is a signal-based 2-way data binding, like ngModel, but for any component input. With that approach, all we change in the dialog is to call model() instead of signal() when initializing isOpen:
export class DialogComponent {
isOpen = model(false);
title = input("Title");
closePopup(): void {
this.isOpen.set(false);
}
}
Then, in the parent component, we’re getting back to something closer to our initial approach, except that the non-obvious onClose output is gone:
<button (click)="showDialog = true">Open dialog</button>
<app-dialog title="Test dialog" [(isOpen)]="showDialog">
This dialog is great
</app-dialog>
Note the syntax (isOpen) indicating a 2-way data binding, making it clear that the value of showDialog is always going to be in sync with the value of isOpen. We still have our dialog as a presentation component, the API is both explicit and minimalistic, and the amount of code to write and maintain is optimal.
You can find the code for this third approach on Stackblitz here.
As we saw, all three options work and have their pros and cons. The third one wins because it has the least amount of code while checking all the boxes in terms of reusability, API simplicity, architecture concepts (coupling, presentation component), and compatibility with onPush (which means zoneless-ready, too).
That said, getting to that approach might not have been obvious at first, which is why keeping an eye open for new framework features and understanding how they can be used in ways that simplify our code is always a good idea.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Building Reusable Components with React 19 Actions
Build reusable React components with React 19 Actions using useTransition() and useOptimistic(). Learn how to track pending states, implement optimistic updates, and expose action properties for custom logic in the Next.js App Router with practical examples.
Aurora Scharff
Oct 28, 2025

Falling with Style
Learn how Vue.js fall-through attributes work, when they're useful, and common pitfalls to avoid. Master class, style, and event handling in components.
Abdel Awad
Oct 28, 2025

Use Lighthouse to improve your Angular applications
Angular developers often focus on code structure and framework mastery—but end users care most about speed, accessibility, and visibility. This article highlights how tools like Google Chrome’s built-in Lighthouse can help you measure and improve your app’s performance, accessibility, and SEO. By running quick audits and reviewing actionable insights, developers can bridge the gap between technical excellence and real-world user experience.
Alain Chautard
Oct 24, 2025
We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.
Let us help you get the resources you need.
