
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.

Security in React Applications
Learn how to secure React apps: prevent XSS with DOMPurify, store tokens safely in HttpOnly cookies, validate server inputs with Zod, and configure Content Security Policy.
Aurora Scharff
May 7, 2026

Is Nuxt something for “me”?
Using Nuxt vs Not Using Nuxt: A side-by-side comparison of building the same features in plain Vue and Nuxt, so you can see exactly what the framework gives you.
Reza Baar
May 6, 2026

RxJS to Angular Signals: Patterns, Pitfalls, and Practical Tips
Migrating from RxJS to Signals in Angular? This guide breaks down common patterns, practical tips, and step-by-step strategies to help you transition smoothly from reactive streams to a signals-based architecture.
Alain Chautard
May 5, 2026
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.
