skip to content

What are Angular's extended diagnostics, such as NG8101 invalidBananaInBox, and how do you change their severity?

level: middleimportance: should knowfreq 30%

answer

  1. valid code, likely a mistake
  2. the NG81xx family
  3. warning unless configured
  4. checks map plus defaultCategory

basics

~20 s

Extended diagnostics are Angular compiler checks for template code that compiles but is probably wrong, like ([value]) instead of [(value)] (NG8101) or {{ count }} for a signal (NG8109). They warn by default, need strictTemplates, and are set to error, warning or suppress in angularCompilerOptions.extendedDiagnostics.

solid answer

~40 s

Extended diagnostics catch **valid but suspicious** template patterns. Examples: `NG8101 invalidBananaInBox` for `([value])="x"`, which is really an event binding named `[value]`; `NG8109 interpolatedSignalNotInvoked` for `{{ count }}` where `count` is a signal; `NG8102`/`NG8107` for `??` or `?.` on a value that cannot be null; `NG8111` for an event binding that names a method without calling it; `NG8113` for unused entries in a standalone `imports` array. They only run when `strictTemplates` is on, and some also need `strictNullChecks`. Each is a **warning** by default, so the build still succeeds. Severity is set under `angularCompilerOptions.extendedDiagnostics`: `checks` maps a diagnostic's name to `error`, `warning` or `suppress`, and `defaultCategory` covers the rest. The caveat: Angular adds new diagnostics in minor releases, so `"defaultCategory": "error"` can turn a minor upgrade into a failing build.

go deeper

for a junior

Recognise the banana-in-a-box mistake and know that Angular warns about several valid-but-wrong template patterns.

for a middle

Describe a few diagnostics, their strictTemplates precondition, and how checks and defaultCategory set error, warning or suppress.

for a senior

Choose which diagnostics to promote to errors, and plan upgrades around new diagnostics landing in minor releases.

for a principal

Balance strict enforcement against upgrade friction across many repositories, with a shared configuration and a review step for new diagnostics.

## Valid code that is probably a bug Some template code is perfectly legal to the parser and the type checker yet almost never does what its author meant. **Extended diagnostics** are the Angular compiler's checks for these patterns. They carry codes in the `NG81xx` range (plus a few others, such as `NG8021` for misconfigured `@defer` triggers) and each has a camelCase **name** used in configuration. ## A tour of the common ones | Code | Name | Pattern it catches | | :-- | :-- | :-- | | `NG8101` | `invalidBananaInBox` | `([value])="x"`: an event binding named `[value]`, not two-way binding | | `NG8102` | `nullishCoalescingNotNullable` | `a ?? b` where `a` can never be null, so `b` is dead | | `NG8107` | `optionalChainNotNullable` | `a?.b` where `a` can never be null | | `NG8109` | `interpolatedSignalNotInvoked` | `{{ count }}` where `count` is a signal and needs `count()` | | `NG8111` | `uninvokedFunctionInEventBinding` | an event binding that names a method without calling it | | `NG8113` | `unusedStandaloneImports` | a class in a component's `imports` that its template never uses | | `NG8115` | `uninvokedTrackFunction` | `@for (...; track trackByName)` without calling the function | | `NG8117` | `uninvokedFunctionInTextInterpolation` | `{{ getLabel }}` instead of `{{ getLabel() }}` | The banana-in-a-box mnemonic is the classic interview hook: the parentheses (the banana) go **inside** the square brackets (the box), `[(value)]`. Reversed, the binding has no effect at all. ## Preconditions - **`strictTemplates` must be enabled.** Extended diagnostics rely on the precise template types strict mode produces; with it off, none of them emit. Since Angular 22 strict mode is the default, so new projects see them automatically. - **Some checks need `strictNullChecks`.** `nullishCoalescingNotNullable` and `optionalChainNotNullable` only make sense when TypeScript distinguishes `null`; without it, they silently do not run. ## Configuring severity Each diagnostic has one of three categories: 1. **`warning`** (the default) - printed, but the build succeeds. 2. **`error`** - fails the compilation with a non-zero exit code. 3. **`suppress`** - not emitted at all. Configuration lives in `angularCompilerOptions`: ```json { "angularCompilerOptions": { "extendedDiagnostics": { "checks": { "invalidBananaInBox": "error", "unusedStandaloneImports": "suppress" }, "defaultCategory": "warning" } } } ``` `checks` overrides individual diagnostics by **name** (not by code); `defaultCategory` applies to every diagnostic not listed. ## The semver caveat The Angular team adds or enables new extended diagnostics in **minor** releases, so an upgrade can surface new warnings. With `"defaultCategory": "error"`, those become build failures on a minor bump, which feels like a breaking change even though semver was followed. Upgrades can also make an existing check fire more often: the Angular 22 release notes warn that improved narrowing of safe navigation triggers `nullishCoalescingNotNullable` and `optionalChainNotNullable` on existing projects, and suggest disabling those two temporarily if needed. A pragmatic policy: - keep `defaultCategory` at `warning`; - promote the unambiguous bug patterns (`invalidBananaInBox`, `interpolatedSignalNotInvoked`, the uninvoked-function checks) to `error` by name; - review new warnings as part of each upgrade. ## Fixing what they flag - **`NG8101`**: swap the brackets, `[(value)]="name"`. For a signal-based two-way binding the target is a `model()` on the child and the parent binds a writable signal. - **`NG8109`**: call the signal, `{{ count() }}`. - **`NG8102` / `NG8107`**: decide which side is wrong. If the value really can be missing, add `null` or `undefined` to its type; if it cannot, delete the `??` fallback or the `?.`. - **`NG8111` / `NG8117` / `NG8115`**: add the missing call parentheses so the function actually runs. - **`NG8113`**: remove the unused class from the component's `imports` array, which also trims compile work. ## Where they fit Extended diagnostics are about **correctness**, not style: the team explicitly leaves formatting and conventions to linters. They complement type checking - `NG8109` is not a type error, because interpolating a signal function is legal, but it is almost certainly not what the author wanted.

  • Why does {{ count }} with a signal compile in Angular, and what does NG8109 add?
    A signal is a zero-argument function, and interpolating a function is legal: the template would display the function's string form rather than its value. Type checking has nothing to reject. NG8109, `interpolatedSignalNotInvoked`, recognises the pattern and warns that the signal should be called as `count()`.
  • Why can setting extendedDiagnostics.defaultCategory to error break an Angular minor upgrade?
    New extended diagnostics ship in minor versions. With `error` as the default, any new check that fires on existing code fails the build after an otherwise routine update. Keeping the default at `warning` and promoting known checks by name avoids this while still enforcing the important ones.

Extended diagnostics are a spell checker for words that are spelled correctly but wrong in context, like 'their' for 'there': the sentence is valid, but you almost certainly meant something else.

saying these in an interview costs you the question

  • Extended diagnostics are errors by default and block the build.
  • They run even with strictTemplates disabled.
  • Severity is configured by NG code numbers in angular.json.
  • ([value]) is just an alternative spelling of two-way binding.
  • Extended diagnostics enforce code style such as naming and formatting.