Component Events
It is now time to improve our Header
component.
Let's provide a way to set the content text for the component to display when rendered,
and a simple click event contentClick
that gets emitted every time user clicks the content.
// src/app/panel-header/panel-header.component.ts
import { /*...,*/ Input, Output, EventEmitter } from '@angular/core';
@Component({...})
export class PanelHeaderComponent {
@Input()
content = 'Panel header';
@Output()
contentClick = new EventEmitter();
onContentClicked() {
console.log('panel header clicked');
this.contentClick.next();
}
}
From the earlier chapters, you already know that we use @Input
decorator
for the class properties we want to set or bind from the outside.
We also need to use the @Output
decorator to mark our events.
Output Events
You can get more details on the component events in the Output Events section of the Components chapter.
Also, note that in the example above we also add the onContentClicked
method that is going to raise our contentClick
event.
Below is an example of a minimal Header
component template we can use to display a content
value:
<!-- src/app/panel-header/panel-header.component.html -->
<p (click)="onContentClicked()">
{{ content }}
</p>
As you can see, we link the click
event of the enclosed p
element with the onContentClicked
handler
that temporarily sends a message to the console log for testing/debugging purposes
and also invokes the contentClicked
event that other components can use.
Also, we set a default value for the content
property to the "Panel header" string.
So at the runtime, the content or application main page is going to look similar to the following:
Panel header
panel works!
panel-footer works!
However, we are not going to use the Header
component directly.
It is the Panel
component that needs it.
So we should allow our Panel
component to control the header content, and also react on header click events.
Let's toggle the panel content as an example.
Edit the panel component class and add the header
input property to hold the text for the Header
,
and displayBody
property to serve as a flag for showing and hiding the main panel content,
like in the example below:
// src/app/panel/panel.component.ts
import { /*...,*/ Input } from '@angular/core';
@Component({...})
export class PanelComponent {
@Input()
header = 'My panel header';
displayBody = true;
}
For the next step, let's update the panel component template
to link the Header
properties with the newly introduced class members:
<!-- src/app/panel/panel.component.html -->
<app-panel-header
[content]="header"
(contentClick)="displayBody = !displayBody">
</app-panel-header>
<ng-container *ngIf="displayBody">
<p>
panel works!
</p>
</ng-container>
<app-panel-footer></app-panel-footer>
You can now run the application and test your components by clicking the panel header text multiple times. The panel should toggle its body content every time a header gets clicked.
Below is how the panel should look like by default, in the expanded state:
My panel header
panel works!
panel-footer works!
Also, the next example shows how the panel looks like in the collapsed
state:
My panel header
panel-footer works!
Congratulations, you just got the component events working, and tested them in practice.
Now feel free to extend the PanelFooterComponent
and add similar content
and contentClick
implementations.
Bubbling Up Child Events
We have created a PanelHeader
component earlier in this chapter.
Also, we introduced a click
event for the component and made the Panel
component host it within its template,
and toggle panel body content every time the Panel Header
is clicked.
Imagine that the <app-panel>
is a redistributable component, and you would like developers to have access to header clicks as well.
The <app-panel-header>
however, is a child element, and developers do not have direct access to its instance when working with the Panel
.
In this case, you would probably want your main Panel
component re-throwing its child events.
We already got the header
and the footer
input properties that hold the values for the <app-panel-header
and <app-panel-footer>
elements.
Let's now introduce two new output events and call them headerClick
and footerClick
.
// src/app/panel/panel.component.ts
export class PanelComponent {
displayBody = true;
@Input()
header = 'My panel header';
@Input()
footer = 'My panel footer';
@Output()
headerClick = new EventEmitter();
@Output()
footerClick = new EventEmitter();
onHeaderClicked() {
this.displayBody = !this.displayBody;
this.headerClick.next();
}
onFooterClicked() {
this.footerClick.next();
}
}
As you can see from the code above, we also get two methods to raise our events.
The onHeaderClicked
method is still toggling the panel body before raising the headerClick
event.
Next, our <app-panel>
component is going to watch the contentClick
events of the child elements, and emit events for developers.
Update the HTML template and subscribe to the header and footer events like in the example below:
<!-- src/app/panel/panel.component.html -->
<app-panel-header
[content]="header"
(contentClick)="onHeaderClicked()">
</app-panel-header>
<ng-container *ngIf="displayBody">
<p>
panel works!
</p>
</ng-container>
<app-panel-footer
[content]="footer"
(contentClick)="onFooterClicked()">
</app-panel-footer>
Finally, let's test our panel events in action. Update your main application template and subscribe to our newly introduced events for header and footer clicks:
<!-- src/app/app.component.html -->
<app-panel
(headerClick)="onHeaderClicked()"
(footerClick)="onFooterClicked()">
</app-panel>
For the sake of simplicity we are going to log messages to browser console similar to the following:
// src/app/app.component.ts
@Component({/*...*/})
export class AppComponent {
onHeaderClicked() {
console.log('App component: Panel header clicked');
}
onFooterClicked() {
console.log('App component: Panel footer clicked');
}
}
If you compile and run your web application with the ng serve --open
command,
you should be able to see messages in the console every time a header or footer elements of the panel get clicked.