Angular’s Extended Diagnostics: The Key to Boosting Your Code Quality
Hello there!
In a previous Angular version (specifically, Angular v13), a significant new feature has been added to the compiler: extended diagnostics. Let’s explore this enhancement in more detail.
Why Extended Diagnostics Matter in Angular
As modern web applications grow increasingly complex, maintaining clean, efficient, and error-free code becomes more challenging. Angular introduces a powerful tool to help developers catch subtle issues early: extended diagnostics.
Unlike traditional TypeScript errors, which often focus on critical compilation issues, extended diagnostics in Angular delve deeper, identifying potential problems before they manifest. Whether it’s preventing unintended behaviors or guiding developers toward best practices, this feature ensures your code remains robust and maintainable in the long run.
Severities — Extended diagnostics configurations
Before we delve into configuration, let’s understand the different severity levels.
Extended diagnostics are warnings by default and do not block compilation. Each diagnostic can be configured with one of the following severities:
- warning: The compiler emits the diagnostic but continues compilation. A warning severity won’t cause compilation failure. In this case, a yellow message is shown in your console.
- error: The compiler emits the diagnostic and fails the compilation. This results in a non-zero exit code. Using this severity, a red message is shown in your console (in addition to failing the compilation).
- suppress: The compiler completely hides the diagnostic.
Setting up: Enabling extended diagnostics
To enable extended diagnostics in your Angular project, you need to configure it within your tsconfig.json
file under the angularCompilerOptions
. Below is an example of how you can add this setting:
{
"angularCompilerOptions": {
"extendedDiagnostics": {
"checks": {
//use your prefer setting
"invalidBananaInBox": "error",
"nullishCoalescingNotNullable": "warning",
"missingControlFlowDirective": "error",
"textAttributeNotBinding": "warning",
"missingNgForOfLet": "error",
"suffixNotSupported": "warning",
"optionalChainNotNullable": "warning",
"skipHydrationNotStatic": "error",
"interpolatedSignalNotInvoked": "warning",
"uninvokedFunctionInEventBinding": "suppress"
}
}
}
}
This configuration enables extended diagnostics and defines different levels (error
, warning, suppress
) for specific checks. You can customize it according to your project’s needs.
Real-World Examples of Extended Diagnostics
Let’s dive into some of the most useful diagnostics checks Angular offers:
1. NG8101: invalidBananaInBox
This error occurs when you incorrectly bind to an input event using banana-in-a-box syntax [(...)]
. This syntax is used for two-way data binding, and improper use can lead to unexpected behavior.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<input ([ngModel])="userName">`,
})
class MyComponent {}
The correct usage should be:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<input [(ngModel)]="userName">`,
})
class MyComponent {}
You have to notice that the incorrect example we’re using ([var]) instead of [(var)]. In case to use ([var]), you are doing an event binding to a model variable called [var].
2.NG8102: nullishCoalescingNotNullable
This diagnostic detects a useless nullish coalescing operator (??
) characters in Angular templates. Specifically, it looks for operations where the input is not "nullable", meaning its type does not include null
or undefined
. For such values, the right side of the ??
will never be used.
Example:
import {Component} from '@angular/core';
@Component({
template: `<div>{{ ng8102 ?? "default text" }}</div>`,
})
class MyComponent {
ng8102: string | null = 'ng8102';
}
The correct usage should be:
import {Component} from '@angular/core';
@Component({
template: `<div>{{ ng8102 }}</div>`,
})
class MyComponent {
ng8102: string = 'ng8102';
}
In this case, you have to notice that using the nullish coalescing operator with a non-nullable input has no effect, revealing a mismatch between the value’s allowed type and its template presentation.
3. NG8103: missingControlFlowDirective
This error is thrown when Angular expects a control flow directive like *ngIf
or *ngFor
, but none is found. This can happen when you forget to include the directive on elements that require them.
Example:
import {Component} from '@angular/core';
@Component({
standalone: true,
imports: [],
template: `<div *ngIf="visibleNg8103">ng8103</div>`,
})
class MyComponent {
visibleNg8103 = true;
ng8103: string = 'ng8103';
}
The correct usage should be:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<div *ngIf="visibleNg8103">ng8103</div>`,
})
class MyComponent {
visibleNg8103 = true;
ng8103: string = 'ng8103';
}
In this case, you have to notice that you are not importing the correct module; therefore, Angular tries to bind a property with an unknown directive.
4. NG8104: textAttributeNotBinding
This diagnostic warns you when you use a text attribute instead of property binding. In Angular, data binding should generally be done using [property]
syntax rather than relying on static HTML attributes.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<div attr.id="ng8104_id">incorrect</div>`,
})
class MyComponent {}
The recommended approach is:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<div [attr.id]="ng8104_id">Correct</div>`,
})
class MyComponent {}
In this case, you have to notice that you are not binding correctly your element.
When binding toattr.
,class.
, orstyle.
, ensure you use the Angular template binding syntax ([]
).
<div [attr.id]=”my-id”></div>
<div [style.color]=”red”></div>
<div [class.large]=”true”></div>
5. NG8105: missingNgForOfLet
This error points out missing let
declarations when using the *ngFor
directive.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<li *ngFor="item of items">{{ item }}</li>`,
})
class MyComponent {
items = ['item1', 'item2', 'item3'];
}
The correct way:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<li *ngFor="let item of items">{{ item }}</li>`,
})
class MyComponent {
items = ['item1', 'item2', 'item3'];
}
6. NG8106: suffixNotSupported
This warning highlights when you use an unsupported suffix with Angular binding expressions, particularly in directive-based structures.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<img src="incorrect" [attr.width.px]="ng8106" /> <!-- Warning: NG8106 -->`,
})
class MyComponent {
ng8106 = 5;
}
The correct usage should be:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [],
template: `<img src="correct" [attr.width]="ng8106" />`,
})
class MyComponent {
ng8106 = '5px';
}
In this case, the correctly use of directive is [attr.width].
7. NG8107: optionalChainNotNullable
This diagnostic warns when you use optional chaining (?.
) on an expression that can never be null
or undefined
.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<div>Incorrect: {{ ng8107?.name }}</div>`,
})
class MyComponent {
ng8107: { name: string } = { name: 'ng8107' };
}
The correct way:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<div>Correct: {{ ng8107.name }}</div>`,
})
class MyComponent {
ng8107: { name: string } = { name: 'ng8107' };
}
In this case, using the optional chaining operator with a non-nullable input has no effect, indicating a mismatch between the allowed type and its template presentation. Developers may mistakenly assume the output could be null or undefined, leading to confusion about the program’s expected output.
9. NG8109: interpolatedSignalNotInvoked
Angular flags this warning when interpolated signals are not invoked correctly within templates. This diagnostic helps ensure signals function as expected in reactive contexts.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<div>{{ ng8109 }}Incorrect</div>`,
})
class MyComponent {}
It should be:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<div>{{ ng8109() }}Correct</div>`,
})
class MyComponent {}
In this case, the correct usage of any signal in the template is var().
10. NG8111: uninvokedFunctionInEventBinding
This error appears when a function is referenced but not invoked in an event binding.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<button (click)="handleClick">Click me</button>`,
})
class MyComponent {}
The correct way:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `<button (click)="handleClick()">Click me</button>`,
})
class MyComponent {}
11. NG8112: unusedLetDeclaration
This error appears when a @let declaration is declared but never used.
Example:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `@let incorrect =''; `,
})
class MyComponent {}
The correct way:
import {Component} from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
template: `@let correct ='';
<div>{{correct}}</div>`,
})
class MyComponent {}
This error appears when a @let declaration is declared but never used
At the time this document was created, Angular is in the process of adding more configurations for its upcoming v19 release.
Conclusion
Extended diagnostics not only help you prevent errors, but they also guide you toward best practices, improving the maintainability and quality of your code. Activate them and elevate the quality of your Angular applications starting today!
Happy coding, thanks for your attention!
Resources
Disclaimer: code shared in this article is just a basic implementation to
demonstrate each configuration settings.https://github.com/mariodantemariani/angular-18-extended-diagnostics