I have always been meticulous about enforcing strict project conventions and maintaining clean, readable code. In this post, I’ll share additional configuration choices I commonly apply in my own workspaces.
Prettier and ESLint are two tools I rely on heavily to keep code quality high and formatting consistent across Angular workspaces. They help maintain a tidy codebase, uphold coding standards, and make collaboration smoother for the whole team. Below, I’ll walk through configuring both tools in an Angular project, so your code stays both functional and well-formatted.
Before diving in, it’s worth clarifying what each tool handles:
- Prettier handles code formatting, ensuring every developer produces output with the same style.
- ESLint performs static analysis, enforcing code quality rules and best practices.
The baseline: Prettier and ESLint using standard settings (Manfred’s approach)
To get Prettier and ESLint running with their default configurations, take these steps:
Install Dependencies
pnpm i -D prettier eslint
Note: Pick whichever package manager you prefer. I’d personally go with pnpm!
To make the two tools work together seamlessly, add these extra packages:
pnpm i -D eslint-config-prettier eslint-plugin-prettier
Then, bring in the Angular ESLint plugins, which include TypeScript support:
ng add angular-eslint
After installation, your package.json should include these packages (alongside your own dependencies):
{
"devDependencies": {
"angular-eslint": "^19.6.0",
"eslint": "^9.27.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.1",
"prettier": "^3.5.3",
"typescript-eslint": "^8.33.0"
}
}
Create and Verify Configuration Files
Prettier configuration
Add a .prettierrc.json file at the root of your project, containing the following:
{
"singleQuote": true // add this line to enforce single quotes
}
Note: Be sure to strip comments from any JSON files to prevent parsing errors.
- singleQuote: Forces the use of single quotes in JavaScript and TypeScript files.
Most developers prefer single quotes because they’re shorter and don’t require pressing the Shift key. A general guideline: use single quotes for JavaScript/TypeScript, and double quotes for HTML and (S)CSS.
ESLint configuration
Running ng add angular-eslint will create an eslint.config.js file for you. We’re adopting the flat config format rather than .eslintrc.json, as it provides better modularity and extensibility.
Here’s what the generated configuration might look like:
// eslint.config.js
// @ts-check
const eslint = require('@eslint/js');
const tseslint = require('typescript-eslint');
const angular = require('angular-eslint');
const eslintConfigPrettier = require('eslint-config-prettier'); // add the glue plugin
module.exports = tseslint.config(
{
ignores: ['.angular/**', '.nx/**', 'coverage/**', 'dist/**'], // add these ignores
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...angular.configs.tsRecommended,
eslintConfigPrettier, // add the glue plugin
],
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: {},
},
);
Recommended Approach: My Preferred Setup
Prettier Configuration Insights
Consider applying these two extra settings to refine your formatting further:
{
"bracketSameLine": true, // avoid useless/empty final line for multiline html opening tags
"printWidth": 120, // raise line length to 120 characters (80 is too short for big screens)
"singleQuote": true
}
Note: Remove any comments from JSON files to avoid parse errors.
- bracketSameLine: Eliminates additional blank lines when using multiline HTML opening tags, making your Angular templates much neater.
- printWidth: Raises the maximum line width to 120 characters (instead of 80), which is easier on the eyes for widescreen displays.
You can grab this Prettier configuration here.
Prettier Ignore File
Place a .prettierignore file in the project root to tell Prettier which files or directories to skip:
/.angular
/.nx
/coverage
/dist
node_modules
Note: Review your
.gitignorefile to see if there are additional items worth excluding.
Caution: Formatting Existing Codebases ⚠️
Exercise care when introducing Prettier or modifying its settings in an established project: it can reformat many files at once. To keep things tidy, commit only the Prettier-related changes as a separate commit. That allows you to evaluate formatting revisions independently without mixing them with functional updates. 🙂
ESLint Configuration Insights
Next, let’s tighten the ESLint setup and broaden its coverage. This approach helps catch problems early and enforces good practices throughout your codebase. Admittedly, some of these rules are subjective—but they’re valuable for maintaining a sizable enterprise codebase. Tweak or drop rules to suit your team’s needs.
// eslint.config.js
// @ts-check
const eslint = require('@eslint/js');
const tseslint = require('typescript-eslint');
const angular = require('angular-eslint');
const eslintConfigPrettier = require('eslint-config-prettier');
module.exports = tseslint.config(
{
ignores: ['.angular/**', '.nx/**', 'coverage/**', 'dist/**'],
files: ['**/*.ts'],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...angular.configs.tsRecommended,
eslintConfigPrettier,
],
processor: angular.processInlineTemplates,
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'app',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: ['attribute', 'element'],
prefix: 'app',
style: 'kebab-case',
},
],
// Angular best practices
'@angular-eslint/no-empty-lifecycle-method': 'warn',
'@angular-eslint/prefer-on-push-component-change-detection': 'warn',
'@angular-eslint/prefer-output-readonly': 'warn',
'@angular-eslint/prefer-signals': 'warn',
'@angular-eslint/prefer-standalone': 'warn',
// TypeScript best practices
'@typescript-eslint/array-type': ['warn'],
'@typescript-eslint/consistent-indexed-object-style': 'off',
'@typescript-eslint/consistent-type-assertions': 'warn',
'@typescript-eslint/consistent-type-definitions': ['warn', 'type'],
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'no-public',
},
],
'@typescript-eslint/naming-convention': [
'warn',
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
},
],
'@typescript-eslint/no-empty-function': 'warn',
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'@typescript-eslint/no-shadow': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
// JavaScript best practices
eqeqeq: 'error',
complexity: ['error', 20],
curly: 'error',
'guard-for-in': 'error',
'max-classes-per-file': ['error', 1],
'max-len': [
'warn',
{
code: 120,
comments: 160,
},
],
'max-lines': ['error', 400], // my favorite rule to keep files small
'no-bitwise': 'error',
'no-console': 'off',
'no-new-wrappers': 'error',
'no-useless-concat': 'error',
'no-var': 'error',
'no-restricted-syntax': 'off',
'no-shadow': 'error',
'one-var': ['error', 'never'],
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'sort-imports': [
'error',
{
ignoreCase: true,
ignoreDeclarationSort: true,
allowSeparatedGroups: true,
},
],
// Security
'no-eval': 'error',
'no-implied-eval': 'error',
},
},
{
files: ['**/*.html'],
extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility],
rules: {
// Angular template best practices
'@angular-eslint/template/attributes-order': [
'error',
{
alphabetical: true,
order: [
'STRUCTURAL_DIRECTIVE', // deprecated, use @if and @for instead
'TEMPLATE_REFERENCE', // e.g. `<input #inputRef>`
'ATTRIBUTE_BINDING', // e.g. `<input required>`, `id="3"`
'INPUT_BINDING', // e.g. `[id]="3"`, `[attr.colspan]="colspan"`,
'TWO_WAY_BINDING', // e.g. `[(id)]="id"`,
'OUTPUT_BINDING', // e.g. `(idChange)="handleChange()"`,
],
},
],
'@angular-eslint/template/button-has-type': 'warn',
'@angular-eslint/template/cyclomatic-complexity': ['warn', { maxComplexity: 10 }],
'@angular-eslint/template/eqeqeq': 'error',
'@angular-eslint/template/prefer-control-flow': 'error',
'@angular-eslint/template/prefer-ngsrc': 'warn',
'@angular-eslint/template/prefer-self-closing-tags': 'warn',
'@angular-eslint/template/use-track-by-function': 'warn',
},
},
);
Disclaimer: These are my current thoughts, always open to refinement. Feel free to reach out on LinkedIn, bluesky or X.
Download this ESLint configuration.
Integrating Into Your Workflow
To guarantee your code always meets formatting and linting standards, bake Prettier and ESLint into your daily routine. I set Prettier to run automatically on save. This works nicely in Cursor, VS Code, and WebStorm (and even NeoVIM, I believe). Often, I’ll write a line in one go and hit Cmd + S (yes, I’m a Mac user, but I have a Pixel – best combo 😜) to clean it up.
Right before committing, I run ESLint to catch any problems. You can do this manually (my preference) or automate it with a pre-commit hook. This is how it slots into my manual pre-commit routine:
- run
ng lintto spot linting errors - run
ng bto confirm the project compiles - run
ng e2eto verify end-to-end tests pass
If you’re using Nx, swap ng for nx in the commands above.
Workshops
If you’d like to explore Angular in depth, we provide a range of workshops in both English and German.
- Best Practices Workshop 📈 (covering Prettier and ESLint setup)
- Accessibility Workshop ♿
- Performance Workshop 🚀
- NG Styling Workshop 🎨
Wrapping Up
Integrating Prettier and ESLint into your Angular workspace is a simple step with substantial payoff for code quality and maintainability. Following the guidance above will keep your code functional, properly styled, and aligned with industry standards.
This article was authored by Alexander Thalhammer. Discuss it on LinkedIn, bluesky or X.
