After an upgrade to Angular 21, `@HostListener('click') onClick(e: MouseEvent)` fails the build with "Expected 1 arguments, but got 0."; what changed, and what is the right fix?
answer
- host expressions are now compiled like templates
- a compiler option turned on by default
- the args array is the call site
- the old code passed undefined
basics
~20 sSince Angular 21 the typeCheckHostBindings compiler option defaults to true, so host bindings and listeners are type-checked. The method expects an event but the decorator passes none; add ['$event'] to the decorator, or move to the host object.
solid answer
~40 sAngular 21 turned on `typeCheckHostBindings` by default, so expressions in the `host` object and in `@HostBinding`/`@HostListener` are now checked like template expressions. `@HostListener('click')` with no argument list calls `onClick()` with no arguments; before v21 that compiled and `e` was simply `undefined` at runtime, a latent bug. The checker now reports "Expected 1 arguments, but got 0." The right fix is to pass the event: `@HostListener('click', ['$event'])`, or, as the docs recommend, `host: { '(click)': 'onClick($event)' }`. The checker also infers `$event`'s type from the event name, so declaring a parameter of an unrelated type is reported too, and it checks host property bindings against the DOM schema, catching typos such as a missing `attr.` prefix. Setting `typeCheckHostBindings: false` in `angularCompilerOptions` silences all of this and is a temporary escape hatch, not a fix.
go deeper
Recall that since Angular 21 host bindings and listeners are type-checked, and that @HostListener needs ['$event'] to pass the event.
Explain why the old code compiled but passed undefined, and how argument lists, event types and member names are now checked.
Plan the upgrade: fix errors by kind, avoid disabling the option permanently, and convert decorators to the host object as files are touched.
Treat a new default check as a free bug audit: budget the upgrade to fix what it finds rather than suppressing it globally.
## What changed in Angular 21 Angular type-checks templates by generating TypeScript code that mirrors each binding. Before v21 that checking did not cover **host bindings**: the expressions in a component's or directive's `host` object and in `@HostBinding`/`@HostListener` decorators. A typo in a host expression, a wrong argument count or a mismatched event type compiled happily and failed, or silently misbehaved, at runtime. Angular 21 made the compiler option **`typeCheckHostBindings`** default to `true`. The v21 update guide warns that this may surface new build errors and says to resolve them or set the option to `false` in the `angularCompilerOptions` section of `tsconfig.json`. ## Why this snippet fails ```ts @HostListener('click') onClick(e: MouseEvent) { this.lastX = e.clientX; } ``` The second argument of `@HostListener` is the list of expressions passed to the method. Without it, Angular calls `onClick()` with **no arguments**. Before v21 the code compiled, and `e` was `undefined` at runtime, so `e.clientX` threw on the first click. The new check turns that runtime crash into a build error: "Expected 1 arguments, but got 0." ## The right fixes 1. **Pass the event**: `@HostListener('click', ['$event'])`. The array now matches the parameter list. 2. **Better, move to the `host` object**, which Angular's docs recommend over the decorators: `host: { '(click)': 'onClick($event)' }`. The call is written inline, so arguments and parameters are visible side by side. 3. **Remove the parameter** if the method does not need the event. Setting `typeCheckHostBindings: false` also makes the error disappear, but it disables every host check in the project, including the ones catching real bugs. Treat it as a short-term switch during a large upgrade, tracked and removed. ## What else the checker now reports | Mistake | Example | Reported as | | --- | --- | --- | | Too few listener arguments | `@HostListener('click')` on `onClick(e)` | Expected 1 arguments, but got 0. | | Too many listener arguments | `@HostListener('click', ['$event'])` on `onClick()` | Expected 0 arguments, but got 1. | | Wrong event type | `'(click)': 'handle($event)'` where `handle(e: KeyboardEvent)` | The inferred event type is not assignable to `KeyboardEvent` | | Unknown member | `'[attr.aria-label]': 'lable'` | Property 'lable' does not exist on the class | | Unknown DOM property | `'[foo]': '123'` on a component | Can't bind to 'foo' since it isn't a known property | A few details make the checks useful: - **Event types are inferred from the event name**, as in templates, including for global targets such as `document:click`. A click event is typed as a pointer event, which is a subtype of `MouseEvent`, so a `MouseEvent` parameter is accepted. - **The element type comes from the selector** where possible: a directive with selector `input[appMask]` is checked against `<input>`'s properties. - **Template locals do not leak in.** A `@let` or `#ref` declared in the component's template is not visible to host expressions, which run against the class only. ## Why the old behaviour was a trap The unchecked version failed in the worst possible way. The build passed, unit tests that never clicked the host passed, and the first real click threw a `TypeError` reading `clientX` of `undefined`. Worse variants did not throw at all: a handler that only forwarded `e` to a service, or checked `e?.key`, silently did nothing. The type check moves all of these from production to the compiler, which is why the release made it the default instead of leaving it opt-in. It also catches drift over time. When a method's parameters change during a refactor, the host listener's argument list is a string the TypeScript compiler alone never looked at; with host type checking, Angular's compiler reports the mismatch at the call site. ## How to roll it out on a large codebase - Upgrade, build, and group the new errors by kind; most are argument-count and misspelled-member errors that are one-line fixes. - Fix wrong event types by correcting the parameter type rather than casting. - If the list is too long for one change, set the option to `false` temporarily, fix module by module, and turn it back on. - Use the occasion to convert touched decorators to the `host` object.
- Is `@HostListener('click', ['$event']) onClick()` also an error under the new checks?Yes. The checker compares the argument list with the method's parameters in both directions, so passing `$event` to a method that declares no parameters is reported as "Expected 0 arguments, but got 1." Remove the argument or add the parameter.
- Why does a `MouseEvent` parameter type-check for a `click` host listener even though the inferred event type is a pointer event?The DOM typings describe the click event as a `PointerEvent`, which extends `MouseEvent`. Passing a subtype where the supertype is expected is assignable, so the check passes. A `KeyboardEvent` parameter is not a supertype and is reported.
saying these in an interview costs you the question
- Setting typeCheckHostBindings to false is the recommended fix for new host errors.
- @HostListener passes the event automatically, so the args array is optional for the event.
- Host expressions can read #refs and @let variables declared in the component template.
- The upgrade broke working code; the listener behaved correctly before v21.
- Only the host object is checked; decorator-based bindings are still unchecked.