What are Angular's extended diagnostics, such as NG8101 invalidBananaInBox, and how do you change their severity?
answer
- valid code, likely a mistake
- the NG81xx family
- warning unless configured
- checks map plus defaultCategory
basics
~20 sExtended 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 sExtended 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
Recognise the banana-in-a-box mistake and know that Angular warns about several valid-but-wrong template patterns.
Describe a few diagnostics, their strictTemplates precondition, and how checks and defaultCategory set error, warning or suppress.
Choose which diagnostics to promote to errors, and plan upgrades around new diagnostics landing in minor releases.
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.