Logo
Published on

How to Setup ESLint and Prettier in an Angular Project

Authors
  • Name
    Twitter

vacuuming confetti

ESLint is the recommended code linting tool for Angular applications to help ensure good code quality and prevent bugs before they happen. For example, the no-const-assign rule prevents you from modifying a const, which will cause a runtime error:

showing a cannot assign to constant error

Prettier is a popular code formatting tool which formats code in a way that promotes readability and helps development shops achieve consistency between code bases. For example, Prettier changes this:

 <div><p><span>Hello World</span>  
    </p></div>

to this:

<div>  
  <p><span>Hello World</span></p>  
</div>

A trivial example, but multiply instances like these by a thousand and you can imagine the impact Prettier can have on the readability and consistency of a code base.

While ESLint has some formatting rules as well, many people prefer Prettier due to its more opinionated nature, which leaves fewer things to be configured — i.e., fewer decisions to be made. Prettier also formats files other than JavaScript/TypeScript out of the box. Overall, Prettier is a more comprehensive code formatting tool.

Taken together, ESLint and Prettier help ensure consistent, high-quality code bases that follow best practices.

In this post, I want to talk about how to configure ESLint and Prettier for an Angular application. I’ll also briefly explore how to publish shared configuration so that all of your applications can follow the same linting and formatting standards.

Code linting and formatting are in many ways about reducing the number of options available when writing code, but ironically, there are a lot of options when it comes to configuring these tools. Rather than list all of the options available when detailing how to set these up, I’m going to provide an opinionated setup that I think is common.


Adding ESLint

The first step in adding ESLint to an Angular application is to run this command:

ng add @angular-eslint/schematics

You can optionally pass a projectName parameter, but if you have a single project in the repo, ESLint will be configured automatically, which entails:

Adding dependencies:

Adding/Updating config files:

  • Adds a lint npm task to package.json which lints the project
  • Adds the lint target to angular.json as well as a schematics entry under cli so that future apps and libs in the workspace will be automatically configured to use ESLint.
  • Adds an .eslintrc.json file that extends from eslint, typescript-eslint and angular-eslint which applies the linting rules those packages provide. It also adds rules to make sure directive and component selectors are prefixed with app which can be changed if necessary. This file is where the actual linting rules come from.

With this step complete, you can now run ng lint or npm run lint and enforce the recommended linting rules from eslint, typescript-eslint and angular-eslint.

Adding Prettier

Prettier recommends installing prettier locally in your project with the exact version:

npm install prettier -D --save-exact

Next add a .prettierrc.json file to house the Prettier options (what few there are) and a .prettierignore file to specify which files/directories should be ignored (note that node_modules is ignored by default):

example .prettierrc.json and .prettierignore

Adding Prettier ESLint Config

Some ESLint rules conflict with Prettier ones, so Prettier has published an eslint-config that disables those rules. Run the following:

npm install -D eslint-config-prettier

Now you just have to extend from prettier in .eslintrc.json (make sure it’s the last entry):

"extends": [  
  ...  
  "prettier"  
],

Editor Configuration

ESLint and Prettier can be run from the command line, but you’ll want to integrate them into your editor of choice.

For VS Code, you can install the ESLint and Prettier — Code formatter extensions.

Press Ctrl + ,and then click the Open Settings (JSON) icon in the top right:

Then add these entries to settings.json:

"editor.codeActionsOnSave": {  
    "source.fixAll.eslint": true  
},  
"editor.defaultFormatter": "esbenp.prettier-vscode",  
"editor.formatOnSave": true

These settings auto-fix ESLint errors on Save (using the ESLint extension), set Prettier as the default formatter for all file types, and auto-format on Save (using the Prettier extension).

Commit Hook

It’s possible to use Husky and lint-staged to automatically fix linting and formatting errors pre-commit. However, I elect not to do that because:

  1. As a matter of principle, I’m not sure how I feel about altering code after the developer has “committed” to “committing” — even if it should have no effect on functionality
  2. It seems a bit slow to me

To me, at least for now, the better option seems to be to have everyone install the ESLint and Prettier extensions and auto-fix|format on Save. You’ll want to run ESLint and Prettier in your CI to block PRs that have lint or format errors, thus encouraging everyone to use the extensions:

ng lint  
npx prettier --check **/* --ignore-unknown

Rules

ESLint

We are now extending the following rule sets for TypeScript files in the .eslintrc.json file. Note that order DOES matter, and if there are conflicting rules, the last one wins.

"files": ["*.ts"],  
"extends": [  
  "eslint:recommended",  
  "plugin:@typescript-eslint/recommended",  
  "plugin:@angular-eslint/recommended",  
  "plugin:@angular-eslint/template/process-inline-templates",  
  "prettier"  
]

And for html files:

"files": ["*.html"],  
"extends": [  
  "plugin:@angular-eslint/template/recommended",   
  "plugin:@angular-eslint/template/accessibility"  
 ],

In addition to these extended rules, you can add any rules you want in the rules object, and they will trump any extended rules. They can be:

eslint rules:

"no-underscore-dangle": ["error"]

typescript-eslint rules:

"@typescript-eslint/member-ordering": ["error"]

or angular-eslint rules:

"@angular-eslint/directive-selector": [  
  "error",  
  {  
    "type": "attribute",  
    "prefix": "mycompany",  
    "style": "camelCase"  
  }  
]

Here’s a complete .eslintrc.json file example:

{  
  "root": true,  
  "ignorePatterns": ["projects/**/*"],  
  "overrides": [  
    {  
      "files": ["*.ts"],  
      "extends": [  
        "eslint:recommended",  
        "plugin:@typescript-eslint/recommended",  
        "plugin:@angular-eslint/recommended",  
        "plugin:@angular-eslint/template/process-inline-templates",  
        "prettier"  
      ],  
      "rules": {  
        "no-underscore-dangle": ["error"],  
        "@typescript-eslint/member-ordering": ["error"],  
        "@angular-eslint/directive-selector": [  
          "error",  
          {  
            "type": "attribute",  
            "prefix": "mycompany",  
            "style": "camelCase"  
          }  
        ],  
        "@angular-eslint/component-selector": [  
          "error",  
          {  
            "type": "element",  
            "prefix": "mycompany",  
            "style": "kebab-case"  
          }  
        ]  
      }  
    },  
    {  
      "files": ["*.html"],  
      "extends": ["plugin:@angular-eslint/template/recommended", "plugin:@angular-eslint/template/accessibility"],  
      "rules": {}  
    }  
  ]  
}

Prettier

Prettier doesn’t have as many configurable options as ESLint and that’s kinda the point. You can see the options that are available to be put in your .prettierrc.json file here. I like these and I think they are common:

{  
  "singleQuote": true,  
  "trailingComma": "es5",  
  "endOfLine": "auto",  
  "bracketSameLine": true,  
  "htmlWhitespaceSensitivity": "ignore"  
}

Sharing Configuration

Finally, I want to explore how you might share ESLint and Prettier configuration among all of your applications by publishing them as scoped npm packages.

ESLint

Create a scoped npm package with a package.json file named @mycompany/eslint-config:

{  
  "name": "@mycompany/eslint-config",  
  "version": "1.0.0",  
  "main": "typescript.json",  
  "peerDependencies": {  
    "@angular-eslint/builder": ">= 15",  
    "@angular-eslint/eslint-plugin": ">= 15",  
    "@angular-eslint/eslint-plugin-template": ">= 15",  
    "@angular-eslint/schematics": ">= 15",  
    "@angular-eslint/template-parser": ">= 15",  
    "eslint": ">= 8",  
    "eslint-config-prettier": ">= 8.8.0",      
    "@typescript-eslint/parser": ">= 5.59.6"  
  }  
}

Note the peer dependencies which are required by the consuming application. We have used >= to allow applications to use newer versions. Applications will want to install the @angular-eslint packages that correspond to their Angular version.

I like to have one file for TypeScript rules and one for HTML rules.

In the typscript.json file below, the “extends” and “rules” objects are copied directly from the local file shown earlier:

{  
  "extends": [  
    "eslint:recommended",  
    "plugin:@typescript-eslint/recommended",  
    "plugin:@angular-eslint/template/process-inline-templates",  
    "plugin:@angular-eslint/recommended",  
    "prettier"  
  ],  
  "rules": {  
    "@angular-eslint/directive-selector": [  
      "error",  
      {  
        "type": "attribute",  
        "prefix": "app",  
        "style": "camelCase"  
      }  
    ],  
    "@angular-eslint/component-selector": [  
      "error",  
      {  
        "type": "element",  
        "prefix": "app",  
        "style": "kebab-case"  
      }  
    ],  
    "@typescript-eslint/member-ordering": ["error"],  
    "no-underscore-dangle": ["error"]  
  }  
}

In html.json, we have again copied from the local file shown earlier:

{  
  "extends": [  
    "plugin:@angular-eslint/template/recommended",   
    "plugin:@angular-eslint/template/accessibility"  
  ]  
}

Publish this package to npm or your company’s local package repo.

Then in the consuming application, install the package, and in the .eslintrc.json file, replace all “extends” entries with just these two:

{  
  "root": true,  
  "ignorePatterns": ["projects/**/*"],  
  "overrides": [  
    {  
      "files": ["*.ts"],  
      "extends": ["@mycompany/eslint-config/typescript"]  
    },  
    {  
      "files": ["*.html"],  
      "extends": ["@mycompany/eslint-config/html"]  
    }  
  ]  
}

Note how we extend from typescript for .ts files and html for .html files.

Prettier

For Prettier, the package name is @mycompany/prettier-config and the package.json looks like this:

{  
  "name": "@mycompany/prettier-config",  
  "version": "1.0.0",    
  "main": "index.json"  
}

and the index.json file is just like a .prettierrc.json file:

{  
  "singleQuote": true,  
  "trailingComma": "es5",  
  "endOfLine": "auto",  
  "bracketSameLine": true,  
  "htmlWhitespaceSensitivity": "ignore"  
}

Publish this package to npm or your company’s local package repo.

Then in your application, install the package, delete the local .prettierrc.json file, and add this (at the top level) to the package.json file:

"prettier": "@mycompany/prettier-config",

That’s it! Now your application is configured to use ESLint for code quality checks, with all of the recommended rules from eslint, typescript-eslint and angular-eslint, and Prettier for code formatting.

If you publish the configuration as npm packages, you can easily share that configuration with all of the Angular applications in your company.


Stray Thoughts

  • Beware .editorconfig and VS Code settings. Prettier will use both of these in the absence of a .prettierrc.json file, so it can be confusing when you’re trying to figure out where settings are coming from. You can see what Prettier is doing by looking in the OUTPUT tab and selecting Prettier from the dropdown.
  • I used to use eslint-plugin-prettier to run Prettier as a lint rule, but I don’t think it adds value anymore, and Prettier no longer recommends it.
  • I used Angular 16 for these examples

Bibliography