This article is part of a series on Inter-Component Communication in Angular. While you can start anywhere, it’s always best to start at the beginning right!
Part 1 – Input Binding
Part 2 – Output Binding
Part 3 – Joining Service
Part 4 – ViewChild
In our previous post, we talked about inter-component communication using the Input declaration, to pass values into a child component. Now, it only makes sense that we talk about using the Output keyword, to pass values from a child component to it’s parent. To me, the Output declaration is easier and more straight forward than Input, but let’s get started and see what you think!
Basic Output Usage
At it’s simplest, we can mark an EventEmitter using the @Output() declaration on a child component like so :
export class ChildComponent {
@Output() message : EventEmitter<string> = new EventEmitter<string>();
sendMessage() {
this.message.next("Hello World!");
}
}
Then when we place this component on any page, we can modify it’s tag to listen for the message event :
<app-child (message)="onMessage($event)"></app-child>
Where “onMessage” is a method on the parent component that should be run when the value is passed back, and $event is the value of the message.
Very easy!
One thing that strangely I find people don’t realize initially, is that your event does not actually need to hold a value. For example, if you want to raise an event from your component when a click event happens, I often see people do something like this :
export class ChildComponent {
@Output() click : EventEmitter<boolean> = new EventEmitter<boolean>();
sendClick() {
this.click.next(true);
}
}
Notice the boolean and returning true? I don’t know why, but I’ve come across this in many projects, sometimes even with number values where they just return “0”. Little do they know, that EventEmitter is quite happy with a void type like so :
export class ChildComponent {
@Output() click : EventEmitter<void> = new EventEmitter<void>();
sendClick() {
this.click.next();
}
}
And that’s honestly all there is to the output keyword. Create an EventEmitter, add the @Output() declaration, listen to it in the parent. Done!
Two-Way Binding
Next we want to talk about two way binding. Two way binding is actually nothing more than syntax sugar for taking values emitted, and automatically setting them a variable. Let me demonstrate with an example.
Imagine I have a child component that looks like so :
export class ChildComponent {
@Output() messageOut : EventEmitter<string> = new EventEmitter<string>();
@Input() message : string;
changeMessage() {
this.messageOut.next("Hello World!");
}
}
Here we can take a message in using the @Input() message line. We can also output a message through the messageOut EventEmitter. All seems pretty normal.
Next, we head to our parent component, and add the control like so :
<app-child [message]="myMessage" (messageOut)="onMessageOut($event)"></app-child>
And the code behind of our parent component :
export class ParentComponent {
myMessage : string = "ABC";
onMessageOut(message : string) {
this.myMessage = message;
}
}
So really all we are doing when we receive a new message out, is setting our variable of “myMessage” to that value. It’s a pretty common use case. And because of that, Angular has come up with a convention to do all of this for you. It works like this.
First we go to our child component, and make sure that the Output variable is called the same as the input variable, with the suffix of change :
export class ChildComponent {
@Output() messageChange : EventEmitter<string> = new EventEmitter<string>();
@Input() message : string;
changeMessage() {
this.messageChange.next("Hello World!");
}
}
Then we head to our child component, and we can essentially wrap the binding with both the [] and () :
<app-child [(message)]="myMessage"></app-child>
Inside our actual Parent code behind, we can remove all event handling :
export class ParentComponent {
myMessage : string = "ABC";
}
And this now works just the same! Angular uses the convention of an output variable named the same as an input, but with the suffix of “change” to mean that when next is called on the EventEmitter, you want the value to be set on the bound variable.
It’s actually fairly simple once you know it, but I’ve seen some really roundabout ways to achieve the same thing. Generally people actually ask things like “How do I name the output variable the same as the input” or “How do I combine input and output into one variable like ngModel?”. And what they actually mean is how do I two way bind in Angular!
Two Way Binding While Keeping Events
I just want to make a quick note on the work above. Commonly people tell me they don’t want to use two way binding because they still need an event to know when the value changes. Two way binding does not hide the event at all.
In our example above, we can actually still use the following :
<app-child [(message)]="myMessage" (messageChange)="onMessageChange($event)"></app-child>
So we get two way binding on the myMessage variable, but we can still listen for the event too! The order of events is that myMessage will be set by Angular before the messageChange event is run. I wouldn’t rely on this, but it’s the way it works right now.
You can even see this in action with ngModel like so :
<input type="text" [(ngModel)]="myMessage" (ngModelChange)="onMessageChange($event)">
New Angular developers tend to think that ngModelChange is some magic event that the Angular team are exposing so you can hook into the ngModel EventEmitter. But actually, it’s just a byproduct of ngModel being a two way bind (Notice that ngModelChange is just ngModel with the change suffix)!
Next
Next we’re going to take a walk through using a “joining service”. A joining service is required when your components may not have a direct relationship between each other (e.g. They are not parent/child). But we can still pass values between components using a service to facilitate the communication! Check it out here : http://angulartut.onpressidium.com/2021/05/01/inter-component-communication-in-angular-joining-service/
💬 Leave a comment