Angular @Output Named After A DOM Event
ID |
javascript.angular_output_dom_event_name |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
medium |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
angular, framework, naming, reliability |
Description
Reports an Angular @Output whose binding name is a standard DOM event name:
import { Component, EventEmitter, Output } from "@angular/core";
@Component({ selector: "app-picker", template: "" })
export class PickerComponent {
// Bad — (click) on this component now means two things.
@Output() click = new EventEmitter<void>();
// Bad — the alias is what the template writes.
@Output("focus") gainedFocus = new EventEmitter<void>();
}
import { Component, EventEmitter, Output } from "@angular/core";
@Component({ selector: "app-picker", template: "" })
export class PickerComponent {
// Good
@Output() itemPicked = new EventEmitter<string>();
@Output() gainedFocus = new EventEmitter<void>();
}
The name checked is the one a template writes: the alias when the decorator declares one, the property name otherwise.
Rationale
A template binds a native event and a component output with exactly the same syntax. On a component that declares an output named click, the binding (click)="pick()" no longer says which of the two it subscribes to.
Angular resolves it to the output. The native click the host element still fires therefore stops reaching that handler, and nothing in the template shows that this is happening — the reader has to open the component to find out.
The failure mode when the output is later removed is worse than a missing binding: the template keeps compiling, the native event takes over, and the handler starts firing on every click of anything inside the component.
Remediation
Name the output after the domain event, not the input gesture that happens to trigger it:
import { Component, EventEmitter, Output } from "@angular/core";
@Component({ selector: "app-picker", template: "" })
export class PickerComponent {
@Output() itemPicked = new EventEmitter<string>();
}
Where the intent really is to re-expose the native event, do not re-emit it at all — let it bubble, and let the consumer bind it on the host element.