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.
Component architecture with inputs, model(), and viewChild()
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.
September 4, 2025
Alain Chautard
React Concurrent Features: An Overview
Learn React's key concurrent features—useTransition, useDeferredValue, Suspense, and useOptimistic—and how they coordinate to create smooth, responsive user experiences. Includes practical examples and best practices.
August 19, 2025
Aurora Scharff
React Children and cloneElement: Component Patterns from the Docs
Explore React's Children utilities and cloneElement API through their excellent documentation. Learn about compound component patterns, understand their limitations, and discover why modern alternatives like render props and context are often better choices for component composition.
August 6, 2025
Aurora Scharff
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.