- Angular
angular-eslintの設定|ng lintの導入とルール一覧
Angular 22 の新しいプロジェクトには lint(コードの書き方の検査)の設定が入っていません。ng add angular-eslint を1回実行すると、eslint.config.js(ESLint の flat config 形式)と angular.json の lint ターゲットが作られ、ng lint が使えるようになります。
ng add angular-eslint # 設定ファイルの作成とパッケージの追加
ng lint # 検査(問題があると終了コード 1)
ng lint --fix # 自動で直せるものを直す
| ng add で起きること | 中身 |
|---|---|
| 作られるファイル | eslint.config.js(1004 バイト) |
angular.json |
lint ターゲット(@angular-eslint/builder:lint)と schematicCollections |
package.json |
"lint": "ng lint" と、devDependencies に4つ(angular-eslint・eslint・@eslint/js・typescript-eslint) |
| 既定で有効な Angular 用ルール | .ts に15個(推奨13+セレクタ名2)、.html に15個(推奨4+アクセシビリティ11) |
確認環境:Angular CLI 22.2.2 で ng new したプロジェクトに ng add angular-eslint を実行しました(angular-eslint 22.6.0、ESLint 10.12.0、typescript-eslint 8.71、TypeScript 6.0.3、Node.js 24.21.0、Linux)。生成ファイル・差分・指摘の文・終了コード・ルールの数は、すべて実際に実行して得たものです。GitHub Actions の YAML は、同じコマンドを並べた例です。ルールの一覧は、インストールした @angular-eslint/eslint-plugin と eslint-plugin-template の各ルールの meta 情報から出しました。仕様は angular.dev の ng lint のページと angular-eslint の GitHub リポジトリの README で確かめました(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「angular-eslintの設定|ng lintの導入とルール一覧」
導入:ng lint を最初に打つと
ng new した直後に ng lint を実行すると、次のメッセージで止まりました。
Cannot find "lint" target for the specified project.
You can add a package that implements these capabilities.
For example:
ESLint: ng add angular-eslint
angular.dev の ng lint のページにも、「ng lint を使うには、lint の機能を持つパッケージを ng add で追加する」と書かれています。メッセージのとおり ng add angular-eslint を実行します。
ng add angular-eslint --skip-confirmation
✔ Searching for compatible package version
› Found compatible package version: 22.6.0.
angular-eslint v22 matches this workspace's Angular v22.
All angular-eslint dependencies have been successfully installed 🎉
We detected that you have a single project in your workspace and no existing linter wired up, so we are configuring ESLint for you automatically.
CREATE eslint.config.js (1004 bytes)
UPDATE package.json (924 bytes)
UPDATE angular.json (2311 bytes)
✔ Packages installed successfully.
Angular 22 のプロジェクトには、angular-eslint の 22 系が選ばれました。angular-eslint の README では、@angular-eslint/ のメジャー版は Angular のメジャー版に合わせる(v12 以降)、とされています。angular-eslint は、ESLint のプラグイン・テンプレート用パーサー・ng lint のビルダー・スキーマ(@angular-eslint/schematics)をまとめたパッケージで、npm ls で見ると、これらが一緒に入っていました。
--skip-confirmation は「パッケージを入れてよいか」の確認を飛ばすオプションです。付けなければ、対話で確認が出ます。
angular.json と package.json の差分
"cli": {
- "packageManager": "npm"
+ "packageManager": "npm",
+ "schematicCollections": ["angular-eslint"]
},
...
"test": {
"builder": "@angular/build:unit-test"
+ },
+ "lint": {
+ "builder": "@angular-eslint/builder:lint",
+ "options": {
+ "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
+ }
}
- "test": "ng test"
+ "test": "ng test",
+ "lint": "ng lint"
...
+ "@eslint/js": "^10.0.1",
+ "angular-eslint": "22.6.0",
+ "eslint": "^10.12.0",
+ "typescript-eslint": "8.71.0",
schematicCollections に angular-eslint が入ったので、以後の ng generate では angular-eslint のスキーマ(ひな形)も候補になります。
生成された eslint.config.js(flat config)
// @ts-check
const eslint = require('@eslint/js');
const { defineConfig } = require('eslint/config');
const tseslint = require('typescript-eslint');
const angular = require('angular-eslint');
module.exports = defineConfig([
{
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
tseslint.configs.recommended,
tseslint.configs.stylistic,
angular.configs.tsRecommended,
],
processor: angular.processInlineTemplates,
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'app',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'app',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
extends: [angular.configs.templateRecommended, angular.configs.templateAccessibility],
rules: {},
},
]);
| 部分 | 意味 |
|---|---|
files: ['**/*.ts'] のブロック |
TypeScript のファイルに当てる設定 |
eslint.configs.recommended |
ESLint 本体の推奨ルール |
tseslint.configs.recommended・stylistic |
typescript-eslint の推奨ルールと、書き方をそろえるルール |
angular.configs.tsRecommended |
Angular 用の推奨ルール(13個) |
processor: angular.processInlineTemplates |
.ts の中の template: \…`` を取り出して、HTML のルールでも調べる |
rules の2つ |
セレクタ名の決まり(コンポーネントは app-xxx、ディレクティブは appXxx) |
files: ['**/*.html'] のブロック |
テンプレートに当てる設定。推奨(4個)とアクセシビリティ(11個) |
angular-eslint の README には、flat config だけに対応していて、以前の .eslintrc 形式には対応しない、と書かれています。.eslintrc.json で書かれた記事の設定は、そのままでは使えません。flat config の書き方全般は、関連記事「ESLint flat config設定|v10対応の完成形コピペ集」で扱っています。
eslint --print-config で、実際に有効になったルールを数えた結果です。
| ファイル | 有効なルール | 内訳 |
|---|---|---|
.ts |
97 | ESLint 本体 49・typescript-eslint 33・angular-eslint 15 |
.html |
15 | テンプレート推奨 4・アクセシビリティ 11 |
既定で有効な Angular 用ルール(28個)と意味
TypeScript 側(tsRecommended:13個)
ルール(@angular-eslint/ を省略) |
既定 | 何を指摘するか |
|---|---|---|
contextual-lifecycle |
error | そのクラスの種類では呼ばれないライフサイクル(Pipe の ngOnInit など) |
no-empty-lifecycle-method |
error | 中身が空の ngOnInit() など |
no-input-rename |
error | @Input('別名') のような入力の別名 |
no-inputs-metadata-property |
error | @Component({ inputs: [...] }) の書き方 |
no-output-native |
error | click など DOM のイベント名と同じ出力名 |
no-output-on-prefix |
error | onSave のような on で始まる出力名 |
no-output-rename |
error | @Output('別名') のような出力の別名 |
no-outputs-metadata-property |
error | @Component({ outputs: [...] }) の書き方 |
prefer-inject |
error | コンストラクタの引数での DI(inject() を勧める) |
prefer-on-push-component-change-detection |
error | 既定の OnPush をやめて Eager/Default にしたコンポーネント |
prefer-standalone |
error | standalone: false |
use-pipe-transform-interface |
error | PipeTransform を implements していない Pipe |
use-lifecycle-interface |
warn | ngOnInit があるのに implements OnInit が無い |
prefer-on-push-component-change-detection の文は「既定の ChangeDetectionStrategy.OnPush をやめないこと」です。@angular/core 22.2.2 の型定義では OnPush に「NOTE: OnPush is enabled by default.」とあり、Default は @deprecated Use Eager instead. になっています。v22 では、何も書かないコンポーネントは OnPush です。OnPush のしくみは、関連記事「Angular変更検知とOnPush|markForCheckの使い方」で扱っています。
テンプレート側(templateRecommended:4個・templateAccessibility:11個)
ルール(@angular-eslint/template/ を省略) |
種類 | 何を指摘するか |
|---|---|---|
banana-in-box |
推奨 | ([value]) のような逆向きの双方向バインディング |
eqeqeq |
推奨 | テンプレートの ==・!= |
no-negated-async |
推奨 | !(obs$ | async) のような async パイプの否定 |
prefer-control-flow |
推奨 | *ngIf・*ngFor・[ngSwitch](@if などを勧める) |
alt-text |
a11y | alt の無い <img> |
click-events-have-key-events |
a11y | キーボードのイベントが無い (click) |
elements-content |
a11y | 中身の無い見出し・リンク・ボタン |
interactive-supports-focus |
a11y | (click) があるのにフォーカスできない要素 |
label-has-associated-control |
a11y | 入力欄と結び付いていない <label> |
mouse-events-have-key-events |
a11y | mouseover・mouseout だけで focus・blur が無い |
no-autofocus |
a11y | autofocus 属性 |
no-distracting-elements |
a11y | <marquee>・<blink> |
role-has-required-aria |
a11y | role に必要な aria-* が足りない |
table-scope |
a11y | <th> 以外の scope 属性 |
valid-aria |
a11y | 存在しない aria-* 属性や、正しくない値 |
a11y はアクセシビリティ(障害のある人も含めて使えるか)のことです。
よく出る指摘と直し方(実際の出力)
わざと間違えたコンポーネントを ng lint にかけました。
// bad.ts(直す前)
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { NgIf, AsyncPipe } from '@angular/common';
import { Observable, of } from 'rxjs';
@Component({
selector: 'my-bad',
imports: [NgIf, AsyncPipe],
templateUrl: './bad.html',
changeDetection: ChangeDetectionStrategy.Default,
})
export class Bad implements OnDestroy {
@Input('userName') name = '';
@Output() onSave = new EventEmitter<void>();
@Output() click = new EventEmitter<void>();
count = 0;
loading$: Observable<boolean> = of(false);
data: any;
constructor(private http: HttpClient) {}
ngOnInit() {}
ngOnDestroy() {
console.log('destroy');
}
}
<!-- bad.html(直す前) -->
<div *ngIf="count == 0">ゼロ</div>
<input ([value])="name">
<img src="logo.png">
<div (click)="onSave.emit()">保存</div>
@if (!(loading$ | async)) {
<p>読み込み完了</p>
}
<label>名前</label><input type="text">
Linting "demo"...
src/app/bad/bad.html
1:7 error Use built-in control flow instead of directive ngIf @angular-eslint/template/prefer-control-flow
1:13 error Expected `===` but received `==` @angular-eslint/template/eqeqeq
2:8 error Invalid binding syntax. Use [(expr)] instead @angular-eslint/template/banana-in-box
3:1 error <img/> element must have a text alternative @angular-eslint/template/alt-text
4:1 error click must be accompanied by either keyup, keydown or keypress event for accessibility @angular-eslint/template/click-events-have-key-events
4:1 error Elements with interaction handlers must be focusable @angular-eslint/template/interactive-supports-focus
5:6 error Async pipe results should not be negated. Use `(observable | async) === false`, `(observable | async) === null`, or `(observable | async) === undefined` to check its value instead @angular-eslint/template/no-negated-async
8:1 error A label component must be associated with a form element @angular-eslint/template/label-has-associated-control
src/app/bad/bad.ts
7:13 error The selector should start with one of these prefixes: "app" (https://angular.dev/style-guide#choosing-component-selectors) @angular-eslint/component-selector
10:44 error Components should not opt out of the default `ChangeDetectionStrategy.OnPush` change detection strategy @angular-eslint/prefer-on-push-component-change-detection
13:10 error Input bindings should not be aliased (https://angular.dev/guide/components/inputs#choosing-input-names) @angular-eslint/no-input-rename
14:13 error Output bindings, including aliases, should not be named "on", nor prefixed with it (https://angular.dev/guide/components/outputs#choosing-event-names) @angular-eslint/no-output-on-prefix
15:13 error Output bindings, including aliases, should not be named as standard DOM events @angular-eslint/no-output-native
18:9 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
20:15 error Prefer using the inject() function over constructor parameter injection. Use Angular's migration schematic to automatically refactor: ng generate @angular/core:inject @angular-eslint/prefer-inject
22:3 warning Lifecycle interface 'OnInit' should be implemented for method 'ngOnInit'. (https://angular.dev/style-guide#use-lifecycle-hook-interfaces) @angular-eslint/use-lifecycle-interface
22:3 error Lifecycle methods should not be empty @angular-eslint/no-empty-lifecycle-method
22:14 error Unexpected empty method 'ngOnInit' @typescript-eslint/no-empty-function
✖ 18 problems (17 errors, 1 warning)
1 error and 1 warning potentially fixable with the `--fix` option.
(表示を見やすくするため、パスと空白を詰めています。)18件のうち、--fix で直せるのは2件でした。 console.log は、既定の設定では指摘されませんでした(no-console は ESLint の推奨に入っていません)。
指摘ごとの直し方
| 指摘(ルール) | 直し方 |
|---|---|
prefer-control-flow |
*ngIf → @if (…) { }。ng generate @angular/core:control-flow で一括移行もできる |
eqeqeq |
== → === |
banana-in-box |
([x]) → [(x)](--fix で直る) |
alt-text |
<img alt="…">。飾りの画像は alt="" |
click-events-have-key-events・interactive-supports-focus |
<div (click)> をやめて <button type="button"> にする |
no-negated-async |
@if ((loading$ | async) === false) |
label-has-associated-control |
<label for="name"> と <input id="name">、または <label> で入力欄を囲む |
component-selector |
selector: 'app-bad' |
prefer-on-push-component-change-detection |
changeDetection の行を消す(v22 は OnPush が既定) |
no-input-rename |
別名をやめ、プロパティ名をそのまま使う |
no-output-on-prefix・no-output-native |
saved・clicked のような名前にする |
no-explicit-any |
型を書く(data?: User) |
prefer-inject |
private http = inject(HttpClient)。ng generate @angular/core:inject で一括移行もできる |
use-lifecycle-interface |
implements OnInit(--fix で直る) |
no-empty-lifecycle-method |
空のメソッドを消す |
--fix で変わったところ
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, Output, OnInit } from '@angular/core';
...
-export class Bad implements OnDestroy {
+export class Bad implements OnDestroy, OnInit {
-<input ([value])="name">
+<input [(value)]="name">
--fix の後の ng lint は ✖ 16 problems (16 errors, 0 warnings) でした。残りは手で直します。
直した後(指摘0件)
// good.ts
import { Component, OnInit, inject, input, output } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { AsyncPipe } from '@angular/common';
import { Observable, of } from 'rxjs';
interface User {
id: number;
name: string;
}
@Component({
selector: 'app-good',
imports: [AsyncPipe],
templateUrl: './good.html',
})
export class Good implements OnInit {
private http = inject(HttpClient);
readonly userName = input('');
readonly saved = output<void>();
readonly clicked = output<void>();
count = 0;
loading$: Observable<boolean> = of(false);
data?: User;
ngOnInit(): void {
this.http.get<User>('/api/me').subscribe((u) => (this.data = u));
}
}
<!-- good.html -->
@if (count === 0) {
<div>ゼロ</div>
}
<input [value]="userName()" />
<img src="logo.png" alt="ロゴ" />
<button type="button" (click)="saved.emit()">保存</button>
@if ((loading$ | async) === false) {
<p>読み込み完了</p>
}
<label for="name">名前</label><input id="name" type="text" />
Linting "demo"...
All files pass linting.
@if などの制御フローは、関連記事「Angular @if・@for・@switchの使い方|ngIfとの対応表」で扱っています。
ルールを足す:型情報が要るルールに注意
既定に入っていないルールを足す例です。
// eslint.config.js の .ts ブロックの rules に追加
'@angular-eslint/no-uncalled-signals': 'error', // signal を () なしで条件に使う誤り
'no-console': ['warn', { allow: ['warn', 'error'] }], // console.log を警告に
// .html ブロックの rules に追加
'@angular-eslint/template/button-has-type': 'error', // <button> に type を
'@angular-eslint/template/prefer-self-closing-tags': 'error', // <app-x></app-x> → <app-x />
no-uncalled-signals を足しただけで ng lint を実行すると、次のエラーで止まりました。
Error when running ESLint: Error while loading rule '@angular-eslint/no-uncalled-signals': You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file. See https://tseslint.com/typed-linting for enabling linting with type information.
型情報(TypeScript の型チェックの結果)を使うルールは、parserOptions の設定が要ります。 .ts のブロックに次を足すと動きました。
processor: angular.processInlineTemplates,
languageOptions: {
parserOptions: {
projectService: true, // tsconfig を自動で探して型情報を作る
tsconfigRootDir: __dirname,
},
},
足したルールで出た指摘です(インラインテンプレートの中も調べられています)。
src/app/bad/extra.ts
14:9 error Doing logic operations on signals will give unexpected results, you probably want to invoke the signal to get its value @angular-eslint/no-uncalled-signals
15:7 warning Unexpected console statement. Only these console methods are allowed: warn, error no-console
6:5 error Type for <button> is missing @angular-eslint/template/button-has-type
7:11 error Use built-in control flow instead of directive ngIf @angular-eslint/template/prefer-control-flow
8:16 error Use self-closing tags for elements with a closing tag but no content @angular-eslint/template/prefer-self-closing-tags
14行目は if (this.open) { … } で、this.open() と書くべきところでした。signal は関数なので、() を付けないと常に真になります。
型情報を使うと、検査は遅くなります。ng new 直後の小さなプロジェクトで ng lint 全体の時間を測ると、型情報なしで約2.8秒、ありで約6.4秒でした。
Prettier との併用
Angular 22 の ng new は、prettier を devDependencies に入れ、.prettierrc(printWidth: 100・singleQuote: true、HTML は angular パーサー)を作ります。angular-eslint の README には「コードの書式(フォーマット)のルールは持たない」とあり、書式は Prettier に任せる分担です。
生成された設定に、Prettier とぶつかるルールがあるかを、eslint-config-prettier 10.1.8 の確認コマンドで調べました。
npx eslint-config-prettier src/app/app.ts src/app/app.html
The following rules are enabled but cannot be automatically checked. See:
https://github.com/prettier/eslint-config-prettier#special-rules
- no-unexpected-multiline
Other than that, no rules that are unnecessary or conflict with Prettier were found.
生成された設定には、Prettier とぶつかるルールは見つかりませんでした(no-unexpected-multiline は、自動では判定できない特別なルールとして挙がっただけです)。今後、書式に関わるルールを足すときのために、最後に eslint-config-prettier を入れておく形が安全です。
npm i -D eslint-config-prettier
const prettier = require('eslint-config-prettier/flat');
module.exports = defineConfig([
// …生成された2つのブロック…
prettier, // 最後に置く:Prettier と重なるルールを切る
]);
この形で ng lint を実行し、All files pass linting. になることを確かめました。書式の検査は ESLint ではなく Prettier で別に行います。
"scripts": {
"lint": "ng lint",
"format": "prettier --write \"src/**/*.{ts,html,scss}\"",
"format:check": "prettier --check \"src/**/*.{ts,html,scss}\""
}
ng new 直後のファイルに prettier --check をかけると、main.ts など5つのファイルが [warn] になりました。導入したら一度 --write で整えておくと、以後の差分が書式の変更で埋もれません。
ng lint のオプション(よく使うもの)
ng lint --help で出た一覧から、よく使うものを抜き出しました。
| オプション | 既定 | 用途 |
|---|---|---|
--fix |
false | 自動で直せるものを直す |
--max-warnings <数> |
-1(無制限) | 警告がこの数を超えたら失敗にする |
--quiet |
false | エラーだけ表示する |
--format <形式> |
stylish | json など ESLint の出力形式 |
--output-file <パス> |
なし | 結果をファイルに書く |
--cache |
false | 変わったファイルだけ調べる(.eslintcache を作る) |
--force |
false | エラーがあっても成功で終える |
--lint-file-patterns |
angular.json の値 | 調べるファイルを指定する |
警告1件だけのファイルで終了コードを比べた結果です。
| コマンド | 終了コード |
|---|---|
ng lint |
0(警告だけなら成功) |
ng lint --max-warnings 0 |
1(Found 1 warnings, which exceeds your configured limit (0).) |
ng lint --format json --output-file lint.json |
0(結果は JSON に書かれた) |
CI での実行
CI(自動テストの仕組み)では、エラーで止めるだけでなく、警告も --max-warnings 0 で止めると、警告が増えていくのを防げます。GitHub Actions の例です。
# .github/workflows/lint.yml
name: lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npx ng lint --max-warnings 0
- run: npx prettier --check "src/**/*.{ts,html,scss}"
ng lintはエラーがあると終了コード 1 を返すので、そのステップで CI が失敗します- 結果をファイルで残したいときは
--format json --output-file lint.jsonを付けて、成果物として保存します - Angular CLI 22.2.2 を Node.js 22.22.2 で動かすと
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.と出て止まりました。CI のnode-versionはこの条件に合わせます
GitHub Actions の基本は、関連記事「CI/CDとは|GitHub Actionsで最初のパイプラインを作るまで」で扱っています。
全ルール一覧(93個)
angular-eslint 22.6.0 に入っている全ルールです。「推奨」は既定で有効なもの、「自動修正」は --fix で直せるものです。
TypeScript 側(51個)
ルール(@angular-eslint/ を省略) |
推奨 | 自動修正 | 内容 |
|---|---|---|---|
component-class-suffix |
クラス名の末尾を Component に(v20 以降は勧められていない) |
||
component-max-inline-declarations |
インラインのテンプレート・スタイルの行数の上限 | ||
component-selector |
生成時 | コンポーネントのセレクタ名の決まり | |
computed-must-return |
値を返さない computed() |
||
consistent-component-styles |
○ | styles・styleUrl(s) の書き方をそろえる |
|
contextual-decorator |
クラスの種類に合わないデコレーター | ||
contextual-lifecycle |
○ | クラスの種類に合わないライフサイクル | |
directive-class-suffix |
クラス名の末尾を Directive に(v20 以降は勧められていない) |
||
directive-selector |
生成時 | ディレクティブのセレクタ名の決まり | |
inject-at-top |
inject() をクラスの先頭にまとめる |
||
no-async-lifecycle-method |
async ngOnInit() など(Angular は待たない) |
||
no-attribute-decorator |
@Attribute を使わない |
||
no-developer-preview |
開発者プレビューの API を使わない | ||
no-duplicates-in-metadata-arrays |
imports などの重複 |
||
no-empty-lifecycle-method |
○ | 空のライフサイクル | |
no-experimental |
実験的な API を使わない | ||
no-forward-ref |
forwardRef を使わない |
||
no-implicit-take-until-destroyed |
注入の文脈の外で DestroyRef なしの takeUntilDestroyed() |
||
no-input-prefix |
入力名の禁止した接頭辞 | ||
no-input-rename |
○ | ○ | 入力の別名 |
no-inputs-metadata-property |
○ | inputs メタデータ |
|
no-lifecycle-call |
ライフサイクルを自分で呼ぶ | ||
no-output-native |
○ | DOM イベント名の出力 | |
no-output-on-prefix |
○ | on で始まる出力名 |
|
no-output-rename |
○ | ○ | 出力の別名 |
no-outputs-metadata-property |
○ | outputs メタデータ |
|
no-pipe-impure |
pure: false の Pipe |
||
no-queries-metadata-property |
queries メタデータ |
||
no-uncalled-signals |
() なしで signal を条件に使う(型情報が要る) |
||
pipe-prefix |
Pipe 名の接頭辞 | ||
prefer-host-metadata-property |
@HostBinding・@HostListener より host |
||
prefer-inject |
○ | コンストラクタ DI より inject() |
|
prefer-on-push-component-change-detection |
○ | ○ | OnPush をやめない |
prefer-output-emitter-ref |
@Output() より output() |
||
prefer-output-readonly |
出力を readonly に |
||
prefer-service-decorator |
○ | @Injectable({ providedIn: 'root' }) より @Service() |
|
prefer-signal-model |
○ | input と output の組より model |
|
prefer-signals |
○ | @Input()・@ViewChild() などより signal |
|
prefer-standalone |
○ | ○ | standalone: false にしない |
reactive-context-must-read-signal |
signal を1つも読まない computed()・effect() |
||
relative-url-prefix |
templateUrl などに ./・../ を付ける |
||
require-lifecycle-on-prototype |
ライフサイクルをインスタンスのプロパティにしない | ||
require-localize-metadata |
$localize に翻訳用の説明を付ける |
||
runtime-localize |
$localize を実行時の翻訳で使える形に |
||
sort-keys-in-type-decorator |
○ | デコレーターのキーの順番 | |
sort-lifecycle-methods |
ライフサイクルを実行順に並べる | ||
use-component-selector |
セレクタを書く | ||
use-component-view-encapsulation |
ViewEncapsulation.None を使わない |
||
use-injectable-provided-in |
providedIn を書く |
||
use-lifecycle-interface |
○(warn) | ○ | ライフサイクルのインターフェース |
use-pipe-transform-interface |
○ | ○ | PipeTransform を implements |
テンプレート側(42個)
ルール(@angular-eslint/template/ を省略) |
推奨 | 自動修正 | 内容 |
|---|---|---|---|
alt-text |
a11y | 代わりの文の無い画像など | |
attributes-order |
○ | 属性・バインディングの順番 | |
banana-in-box |
○ | ○ | 逆向きの ([x]) |
button-has-type |
<button> に type |
||
click-events-have-key-events |
a11y | (click) にキーのイベント |
|
conditional-complexity |
条件式の複雑さの上限 | ||
cyclomatic-complexity |
分岐の数の上限 | ||
elements-content |
a11y | 見出し・リンク・ボタンの中身 | |
eqeqeq |
○ | ○ | ===・!== を使う |
i18n |
○ | i18n 属性の付け忘れなど |
|
interactive-supports-focus |
a11y | (click) の要素をフォーカス可能に |
|
label-has-associated-control |
a11y | <label> と入力欄の結び付け |
|
mouse-events-have-key-events |
a11y | マウスのイベントに focus・blur |
|
no-any |
$any() を使わない |
||
no-autofocus |
a11y | ○ | autofocus を使わない |
no-call-expression |
テンプレートでの関数呼び出し(出力の処理を除く) | ||
no-distracting-elements |
a11y | ○ | <marquee>・<blink> |
no-duplicate-attributes |
同じ入力・イベントの重複 | ||
no-empty-control-flow |
中身の無い @if など |
||
no-inline-styles |
style="…" |
||
no-interpolation-in-attributes |
○ | 属性の {{ }} よりプロパティバインディング |
|
no-negated-async |
○ | async パイプの否定 | |
no-nested-tags |
<p> の中の <p>、<a> の中の <a> |
||
no-non-null-assertion |
テンプレートの ! |
||
no-outerhtml |
outerHTML を使わない |
||
no-positive-tabindex |
正の tabindex |
||
prefer-at-else |
○ | 逆条件の @if より @else |
|
prefer-at-empty |
○ | 別の @if より @for の @empty |
|
prefer-built-in-pipes |
toLowerCase() などより組み込みパイプ |
||
prefer-class-binding |
ngClass より [class] |
||
prefer-contextual-for-variables |
○ | @for の $index などを使う |
|
prefer-control-flow |
○ | *ngIf などより @if |
|
prefer-ngsrc |
<img src> より ngSrc |
||
prefer-self-closing-tags |
○ | 中身の無い要素を <x /> に |
|
prefer-static-string-properties |
○ | 固定の文字列はバインディングにしない | |
prefer-style-binding |
ngStyle より [style] |
||
prefer-template-literal |
○ | 文字列の連結よりテンプレートリテラル | |
require-switch-default |
@switch に @default |
||
role-has-required-aria |
a11y | role に必要な aria-* |
|
table-scope |
a11y | ○ | scope は <th> だけ |
use-track-by-function |
trackBy を使う |
||
valid-aria |
a11y | 正しい aria-* |
「内容」は各ルールの meta 情報(英語の説明)を短く訳したものです。「自動修正」は meta で fixable とされているもので、書き方によっては直りません(上の例の eqeqeq は --fix の後も残りました)。
よくある質問
Q. .eslintrc.json で書かれた設定を使いたいです。
A. angular-eslint の README では、flat config(eslint.config.js)だけに対応するとされています。.eslintrc.json の extends・overrides は、上の生成例のように files ごとのブロックと extends の配列に書き換えます。
Q. 一部のファイルだけ検査から外したいです。
A. defineConfig([...]) の先頭に { ignores: ['src/legacy/**'] } のブロックを置きます。angular.json の lintFilePatterns を狭める方法もあります。
Q. ルールを1行だけ無効にしたいです。
A. TypeScript では // eslint-disable-next-line @angular-eslint/no-input-rename、HTML では <!-- eslint-disable-next-line @angular-eslint/template/alt-text --> のように、ルール名を付けて書きます。2つの <img> の片方の前にだけ HTML のコメントを書くと、指摘はもう片方の1件だけになりました。ルール名を書かないと、その行の全ルールが外れます。
Q. VS Code で保存時に直したいです。
A. ESLint の拡張機能を入れ、設定の editor.codeActionsOnSave に "source.fixAll.eslint": "explicit" を書きます。HTML も調べるには eslint.validate に html を入れます。
まとめ
- Angular 22 の新規プロジェクトには lint が無い。
ng add angular-eslintでeslint.config.jsとlintターゲットが作られた - 設定は flat config。
.tsと.htmlのブロックに分かれ、Angular 用のルールは既定で28個(セレクタ名の2つを足して30個) - わざと間違えたコードで18件の指摘が出て、
--fixで直ったのは2件。残りは表のとおり手で直す - 型情報を使うルール(
no-uncalled-signalsなど)はprojectService: trueが要る。無いとエラーで止まった - 生成された設定には Prettier とぶつかるルールは無かった。念のため最後に
eslint-config-prettier/flatを置く - CI では
ng lint --max-warnings 0で警告も止める
参考・出典
確認日はいずれも 2026年10月13日です。
- Angular「ng lint」:https://angular.dev/cli/lint
- angular-eslint(GitHub リポジトリ・README):https://github.com/angular-eslint/angular-eslint
- angular-eslint のルール一覧(eslint-plugin):https://github.com/angular-eslint/angular-eslint/tree/main/packages/eslint-plugin/docs/rules
- angular-eslint のルール一覧(eslint-plugin-template):https://github.com/angular-eslint/angular-eslint/tree/main/packages/eslint-plugin-template/docs/rules
- typescript-eslint「Linting with Type Information」:https://typescript-eslint.io/getting-started/typed-linting
- eslint-config-prettier:https://github.com/prettier/eslint-config-prettier
- ESLint「Configuration Files」:https://eslint.org/docs/latest/use/configure/configuration-files
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「angular-eslintの設定|ng lintの導入とルール一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。