Default Readme
This project was generated with Angular CLI version 18.2.5.
Run ng serve
for a dev server. Navigate to http://localhost:4200/
. The application will automatically reload if you change any of the source files.
Run ng generate component component-name
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module
.
Run ng build
to build the project. The build artifacts will be stored in the dist/
directory.
Run ng test
to execute the unit tests via Karma.
Run ng e2e
to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
To get more help on the Angular CLI use ng help
or go check out the Angular CLI Overview and Command Reference page.
VS Code Settings
.vscode/settings.json
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"prettier.useEditorConfig": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"cSpell.language": "de-ch, en-gb"
}
Cspell
npm install -g cspell@latest @cspell/dict-de-ch @cspell/dict-en-gb
cspell link add @cspell/dict-de-ch
cspell link add @cspell/dict-en-gb
npm list -g --depth=0
├── @cspell/dict-de-ch@1.2.3
├── @cspell/dict-en-gb@4.1.58
└── cspell@8.17.5
npm install -g cspell@latest
npm install -g @cspell/dict-de-ch
cspell link add @cspell/dict-de-ch
npm install -g @cspell/dict-en-gb
cspell link add @cspell/dict-en-gb
npm list -g --depth=0
Prettier
npm install --save-dev prettier
touch .prettierrc
.prettierrc
{
"printWidth": 240,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"bracketSpacing": true,
"indent_style": "space",
"bracketSameLine": true,
"arrowParens": "always",
"trailingComma": "none",
"singleAttributePerLine": true,
"html.format.wrapAttributes": "force-aligned"
}
touch .prettierignore
.prettierignore
# Ignore artifacts:
dist
build
coverage
# Ignore all HTML files in the root
src/index.html
# Ignore minified files
*.min.js
# Ignore environment configuration files
# This is HIGHLY recommended because characters in passwords will get mangled by Prettier
.env
# Optional
# Ignore all JSON data files
# *.json
# Ignore all Markdown files
# *.md
# Ignore all YAML configuration files
# *.yaml
# *.yml
ESLint
ng add angular-eslint
npm install --save-dev eslint-plugin-unused-imports eslint-plugin-import @stylistic/eslint-plugin
eslint.config.js
// https://github.com/robots4life/angular-prettier-eslint
import eslint from "@eslint/js";
import * as tseslint from "typescript-eslint";
import * as angular from "angular-eslint";
import unusedImports from "eslint-plugin-unused-imports";
import * as importPlugin from "eslint-plugin-import";
import stylistic from "@stylistic/eslint-plugin";
export default tseslint.config(
{
ignores: [
"**/node_modules/**",
"**/dist/**",
"**/coverage/**",
"**/e2e/**",
"**/.angular/**",
"**/.vscode/**",
"**/.github/**",
"**/documentation/**",
"**/tmp/**",
"**/*.min.js",
"**/*.umd.js",
"**/*.es5.js",
"**/karma.conf.cjs",
],
},
{
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
tseslint.configs.recommended,
tseslint.configs.stylistic,
...angular.configs.tsRecommended,
],
processor: angular.processInlineTemplates,
plugins: {
"unused-imports": unusedImports,
import: importPlugin,
"@stylistic": stylistic,
},
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"no-useless-constructor": "error",
"@typescript-eslint/no-explicit-any": "error",
"sort-imports": [
"error",
{
ignoreCase: true,
ignoreDeclarationSort: true,
ignoreMemberSort: true,
allowSeparatedGroups: true,
},
],
"import/order": [
"error",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
named: true,
pathGroups: [
{
pattern: "@angular/**",
group: "external",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["@angular/**"],
alphabetize: {
order: "asc",
caseInsensitive: true,
},
"newlines-between": "always",
},
],
"@stylistic/lines-between-class-members": [
"error",
"always",
{ exceptAfterSingleLine: true },
],
},
},
{
files: ["**/*.html"],
extends: [
...angular.configs.templateRecommended,
...angular.configs.templateAccessibility,
],
rules: {},
}
);
eslint.config.js with comments
// @ts-check
// Allows us to bring in the recommended core rules from eslint itself
import eslint from "@eslint/js"; // <=== use import
// Allows us to use the typed utility for our config, and to bring in the recommended rules for TypeScript projects from typescript-eslint
import * as tseslint from "typescript-eslint"; // <=== use import
// Allows us to bring in the recommended rules for Angular projects from angular-eslint
import * as angular from "angular-eslint"; // <=== use import
// https://github.com/sweepline/eslint-plugin-unused-imports
import unusedImports from "eslint-plugin-unused-imports";
// https://github.com/import-js/eslint-plugin-import
import * as importPlugin from "eslint-plugin-import";
// https://eslint.style/packages/default
import stylistic from "@stylistic/eslint-plugin";
// Export our config array, which is composed together thanks to the typed utility function from typescript-eslint
export default tseslint.config(
// Define ignores to replace .eslintignore file
{
ignores: [
"**/node_modules/**",
"**/dist/**",
"**/coverage/**",
"**/e2e/**",
"**/.angular/**",
"**/.vscode/**",
"**/.github/**",
"**/documentation/**",
"**/tmp/**",
"**/*.min.js",
"**/*.umd.js",
"**/*.es5.js",
"**/karma.conf.cjs",
],
},
{
// Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc)
files: ["**/*.ts"],
extends: [
// https://typescript-eslint.io/getting-started#step-2-configuration
// Apply the recommended core rules
eslint.configs.recommended,
// Apply the recommended TypeScript rules
tseslint.configs.recommended,
// Optionally apply stylistic rules from typescript-eslint that improve code consistency
tseslint.configs.stylistic,
// https://github.com/angular-eslint/angular-eslint/blob/main/packages/angular-eslint/src/configs/README.md
// Apply the recommended Angular rules
...angular.configs.tsRecommended,
],
// Set the custom processor which will allow us to have our inline Component templates extracted
// and treated as if they are HTML files (and therefore have the .html config below applied to them)
processor: angular.processInlineTemplates,
// Override specific rules for TypeScript files (these will take priority over the extended configs above)
// https://github.com/sweepline/eslint-plugin-unused-imports
plugins: {
"unused-imports": unusedImports,
import: importPlugin,
"@stylistic": stylistic,
},
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off",
// Configure @typescript-eslint/no-unused-vars to match unused-imports/no-unused-vars
// This ensures both rules use the same pattern for ignoring variables with underscore prefix
// and prevents conflicting linting errors
"@typescript-eslint/no-unused-vars": [
"error",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"no-useless-constructor": "error",
// turn off no-explicit-any for prod build, during development we want to flag any as error
"@typescript-eslint/no-explicit-any": "error",
"sort-imports": [
"error",
{
ignoreCase: true,
ignoreDeclarationSort: true,
ignoreMemberSort: true,
allowSeparatedGroups: true,
},
],
// https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md
"import/order": [
"error",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
named: true,
pathGroups: [
{
pattern: "@angular/**",
group: "external",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["@angular/**"],
alphabetize: {
order: "asc",
caseInsensitive: true,
},
"newlines-between": "always",
},
],
// https://eslint.style/rules/default/lines-between-class-members
// Enforce newlines between class methods
"@stylistic/lines-between-class-members": [
"error",
"always",
{ exceptAfterSingleLine: true },
],
},
},
{
// Everything in this config object targets our HTML files (external templates,
// and inline templates as long as we have the `processor` set on our TypeScript config above)
files: ["**/*.html"],
extends: [
// Apply the recommended Angular template rules
...angular.configs.templateRecommended,
// Apply the Angular template rules which focus on accessibility of our apps
...angular.configs.templateAccessibility,
],
rules: {},
}
);
package.json
Scripts
"scripts": {
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"lint:check": "npx eslint \"{,src/**/}*.{ts,html,js}\"",
"lint:all": "npx eslint \"{,src/**/}*.{ts,html,js}\" --fix",
"fix": "npm run format; npm run lint:all"
},
Module
},
"type": "module"
}
🎉
This project aims to provide sane defaults with regards to using Prettier and Eslint when working with Angular in a team.
You need to have Node installed locally and available on your terminal.
💡
To easily install Node and to switch between installed versions of Node at ease just use nvm.
Check your current Node version.
node --version
v22.14.0
Check your current NPM version.
npm --version
10.9.2
💡
Depending on the current date you might have to update your Node and NPM version.
Go to a path on your computer where you keep your work projects.
Create a new folder called "angular" there.
mkdir angular
Open VS Code from this new angular
folder.
VS Code has a nifty feature called Profiles.
https://code.visualstudio.com/docs/editor/profiles
https://code.visualstudio.com/docs/editor/profiles#\_create-a-profile
To create a new profile, open the Profiles editor and select the New Profile button.
This opens the New Profile form, where you can enter a profile name, choose an icon, and configure the contents that are included in the new profile.
Create a new Profile and name it Angular.
Once you have created the new profile also select it for the current workspace.
A copy of the Angular Profile is also in this folder https://github.com/robots4life/angular-prettier-eslint/tree/main/vscode-profile.
You can also import a VS Code Profile for your workspace.
Open the Profiles editor and select the dropdown to import a profile.
In the next step you can either select a file or import the profile from a URL.
If you want to import the profile with a URL from this repository you need to use the raw link of where the profile is stored.
https://raw.githubusercontent.com/robots4life/angular-prettier-eslint/refs/heads/main/vscode-profile/Angular.code-profile
https://raw.githubusercontent.com/robots4life/angular-prettier-eslint/refs/heads/main/vscode-profile/Angular.code-profile
Simply paste this link in the dialog and hit enter.
The newly imported profile will be listed in the view with all the profiles.
4. Install Angular CLI, VS Code Extensions Prettier, ESLint, Angular Language Service, Code Spell Checker and Indenticator
Angular CLI
https://angular.dev/tools/cli/setup-local#install-the-angular-cli
npm install -g @angular/cli
https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
Name: Prettier - Code formatter
Id: esbenp.prettier-vscode
Description: Code formatter using prettier
Version: 11.0.0
Publisher: Prettier
Open the Extensions
view by pressing Ctrl + Shift + X
.
Search for Prettier - Code Formatter by Esben Petersen
and click Install
.
In VS Code you can also install the extension via these two commands.
Ctrl + Shift + P
ext install esbenp.prettier-vscode
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
Name: ESLint
Id: dbaeumer.vscode-eslint
Description: Integrates ESLint JavaScript into VS Code.
Version: 3.0.10
Publisher: Microsoft
In VS Code you can also install the extension via these two commands.
Ctrl + Shift + P
ext install dbaeumer.vscode-eslint
https://marketplace.visualstudio.com/items?itemName=Angular.ng-template
Name: Angular Language Service
Id: Angular.ng-template
Description: Editor services for Angular templates
Version: 19.0.3
Publisher: Angular
Open the Extensions
view by pressing Ctrl + Shift + X
.
Search for Angular Language Service
and click Install
.
In VS Code you can also install the extension via these two commands.
Ctrl + Shift + P
ext install Angular.ng-template
https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker
Name: Code Spell Checker
Id: streetsidesoftware.code-spell-checker
Description: Spelling checker for source code
Version: 4.0.34
Publisher: Street Side Software
In VS Code you can also install the extension via these two commands.
Ctrl + Shift + P
ext install streetsidesoftware.code-spell-checker
Code Spell Checker has Dictionaries for Languages
https://github.com/streetsidesoftware/cspell-dicts
First of all let's have a look at your globally installed packages.
Open a terminal and type the following command.
npm list -g --depth=0
https://cspell.org/docs/installation
npm install -g cspell@latest
This will list you all the packages that are currently installed globally for the current version of Node that you are running.
If you switch your Node version do not forget to install the packages you like or need to have installed globally for that Node version.
Here is an example output on a Linux system that uses Node Version Manager.
Node Version Manager is a very handy tool that very easily lets you manage the current version of Node for your development environment.
If you are not using Node Version Manager you are wasting your own time.
From the output we can see that Node version 22.14.0 is used and @angular/cli@19.2.1
as well as npm@10.9.2
are globally installed packages.
/home/user/.nvm/versions/node/v22.14.0/lib
├── @angular/cli@19.2.1
└── npm@10.9.2
For Code Spell Checker to be able to correct spelling in different languages you need to install the corresponding dictionaries as global packages.
Let's install the following dictionaries.
https://github.com/streetsidesoftware/cspell-dicts/tree/main/dictionaries/de_CH
de-ch
npm install -g @cspell/dict-de-ch
cspell link add @cspell/dict-de-ch
Adding:
filename | errors
/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/@cspell/dict-de-ch/cspell-ext.json |
en-gb
npm list -g --depth=0
npm install -g @cspell/dict-en-gb
cspell link add @cspell/dict-en-gb
Adding:
filename | errors
/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/cspell/node_modules/@cspell/dict-en-gb/cspell-ext.json |
npm list -g --depth=0
You terminal will more or less look like this.
user@userbox ~ npm install -g cspell@latest
added 121 packages in 1s
14 packages are looking for funding
run `npm fund` for details
user@userbox ~ npm list -g --depth=0
/home/user/.nvm/versions/node/v22.14.0/lib
├── @angular/cli@19.2.1
├── cspell@8.17.5
└── npm@10.9.2
user@userbox ~ npm install -g @cspell/dict-de-ch
added 1 package in 242ms
user@userbox ~ npm list -g --depth=0
/home/user/.nvm/versions/node/v22.14.0/lib
├── @angular/cli@19.2.1
├── @cspell/dict-de-ch@1.2.3
├── cspell@8.17.5
└── npm@10.9.2
user@userbox ~ cspell link add @cspell/dict-de-ch
Adding:
filename | errors
/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/@cspell/dict-de-ch/cspell-ext.json |
user@userbox ~ npm install -g @cspell/dict-en-gb
added 1 package in 1s
user@userbox ~ cspell link add @cspell/dict-en-gb
Adding:
filename | errors
/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/cspell/node_modules/@cspell/dict-en-gb/cspell-ext.json |
user@userbox ~ npm list -g --depth=0
/home/user/.nvm/versions/node/v22.14.0/lib
├── @angular/cli@19.2.1
├── @cspell/dict-de-ch@1.2.3
├── @cspell/dict-en-gb@4.1.58
├── cspell@8.17.5
└── npm@10.9.2
You have now installed the Swiss German and British English dictionaries for Code Spell Checker.
If you like to remove packages from your global installation you can use these commands. Do not specify a version number when removing global packages as that will fail with their removal.
npm uninstall -g @cspell/dict-de-ch
npm uninstall -g @cspell/dict-en-gb
npm uninstall -g cspell
npm uninstall -g @angular/cli
On Linux you can check the ~/.config/cspell/cspell.json
file for a list of imported dictionaries.
{
"import": [
"/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/@cspell/dict-de-ch/cspell-ext.json",
"/home/user/.nvm/versions/node/v22.14.0/lib/node_modules/cspell/node_modules/@cspell/dict-en-gb/cspell-ext.json"
]
}
Update this file if you remove globally installed dictionaries as you will otherwise try to import a dictionary that is not installed and that will lead to an error.
In order for both of the dictionaries to be enabled in the workspace of your app add this to your /app/.vscode/settings.json file.
"cSpell.language": "de-ch, en-gb"
https://marketplace.visualstudio.com/items?itemName=SirTori.indenticator
Name: Indenticator
Id: SirTori.indenticator
Description: Highlights your current indent depth
Version: 0.7.0
Publisher: SirTori
In VS Code you can also install the extension via these two commands.
Ctrl + Shift + P
ext install SirTori.indenticator
After you have installed all these extensions the Extensions view in your VS Code should look like this.
https://angular.dev/tools/cli/setup-local#create-a-workspace-and-initial-application
Make sure you are inside the angular
folder you previously created as the workspace for this project.
cd angular
Then run the following command.
ng new app
Options
Would you like to share pseudonymous usage data about this project with the Angular Team
at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more
details and how to change this setting, see https://angular.dev/cli/analytics.
no
Global setting: disabled
Local setting: No local workspace configuration file.
Effective status: disabled
? Which stylesheet format would you like to use? CSS [ https://developer.mozilla.org/docs/Web/CSS ]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? no
CREATE app/README.md (1064 bytes)
CREATE app/.editorconfig (274 bytes)
CREATE app/.gitignore (587 bytes)
CREATE app/angular.json (2578 bytes)
CREATE app/package.json (1035 bytes)
CREATE app/tsconfig.json (1012 bytes)
CREATE app/tsconfig.app.json (424 bytes)
CREATE app/tsconfig.spec.json (434 bytes)
CREATE app/.vscode/extensions.json (130 bytes)
CREATE app/.vscode/launch.json (470 bytes)
CREATE app/.vscode/tasks.json (938 bytes)
CREATE app/src/main.ts (250 bytes)
CREATE app/src/index.html (289 bytes)
CREATE app/src/styles.css (80 bytes)
CREATE app/src/app/app.component.css (0 bytes)
CREATE app/src/app/app.component.html (19903 bytes)
CREATE app/src/app/app.component.spec.ts (907 bytes)
CREATE app/src/app/app.component.ts (299 bytes)
CREATE app/src/app/app.config.ts (310 bytes)
CREATE app/src/app/app.routes.ts (77 bytes)
CREATE app/public/favicon.ico (15086 bytes)
✔ Packages installed successfully.
Directory is already under version control. Skipping initialization of git.
https://angular.dev/tools/cli/setup-local#run-the-application
Once you have installed the app in the angular
folder you will see that there is a new folder called app
.
drwxrwxr-x 4 user user 4.0K Jan 10 14:23 .
drwxr-xr-x 75 user user 4.0K Jan 10 14:05 ..
drwxrwxr-x 6 user user 4.0K Jan 10 14:24 app
drwxrwxr-x 7 user user 4.0K Jan 10 14:13 .git
Make sure you are inside this new app
folder.
cd app
Then run the following command.
ng serve
You should have output similar to this on your terminal.
Initial chunk files | Names | Raw size
polyfills.js | polyfills | 90.20 kB |
main.js | main | 18.17 kB |
styles.css | styles | 95 bytes |
| Initial total | 108.47 kB
Application bundle generation complete. [1.550 seconds]
Watch mode enabled. Watching for file changes...
NOTE: Raw file sizes do not reflect development server per-request transformations.
➜ Local: http://localhost:4200/
➜ press h + enter to show help
Open http://localhost:4200/ in a browser of your choice and you will be presented with the default Angular app.
🎉
Prettier is used to format the code, ideally to look prettier, it is used to enforce a style for the syntax.
Prettier does not fix logical issues with your code, it is only used to format your code so that everyone in the team can get used to the code being presented in the same formatting style.
Cognitive load between members of the team is greatly reduced once a good formatting for the code has been found.
Make sure you are inside the new app
folder.
cd app
Install Prettier as a development dependency.
npm install --save-dev prettier
Here your package.json
before you installed Prettier.
Before
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.2"
}
}
Here your package.json
after you installed Prettier.
After
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.3", <=== Prettier is now installed as development dependency
"typescript": "~5.5.2"
}
}
Create a configuration file for Prettier to define the formatting rules.
Create a new file .prettierrc
in the project root directory of the app, so the folder app
.
cd app
touch .prettierrc
If you don't work on the terminal just create the file in VS Code.
Add this configuration to the .prettierrc
file.
{
"printWidth": 240,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"bracketSpacing": true,
"indent_style": "space",
"bracketSameLine": true,
"arrowParens": "always",
"trailingComma": "none",
"singleAttributePerLine": true,
"html.format.wrapAttributes": "force-aligned"
}
You also want to create a .prettierignore
file to specify which files or folders should not be formatted by Prettier.
Create a new file .prettierignore
in the project root directory, so the folder app
.
cd app
touch .prettierignore
Add this configuration to the .prettierignore
file.
# Ignore artifacts:
dist
build
coverage
# Ignore all HTML files in the root
src/index.html
# Ignore minified files
*.min.js
# Ignore environment configuration files
# This is HIGHLY recommended because characters in passwords will get mangled by Prettier
.env
# Optional
# Ignore all JSON data files
# *.json
# Ignore all Markdown files
# *.md
# Ignore all YAML configuration files
# *.yaml
# *.yml
Adjust this Prettier ignore file to your needs.
https://code.visualstudio.com/api/ux-guidelines/command-palette
Open the VS Code Command Palette
Ctrl + Shift + P
Type Format Document With...
.
Then select Configure Default Formatter
and choose Prettier - Code Formatter
.
The project you are working on should have a .vscode
folder under the project root.
So in the folder app
there should already be a .vscode
folder.
This folder has configuration files that tell VS Code how to deal with the current repository you are working on.
Currently this folder has 3 files in it.
.vscode
├── extensions.json
├── launch.json
└── tasks.json
Add a new file settings.json
in this .vscode
folder.
cd app
cd .vscode
touch settings.json
.vscode
├── extensions.json
├── launch.json
├── settings.json <=== new file
└── tasks.json
Add this configuration to the settings.json
file.
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"prettier.useEditorConfig": false
}
Before you can automatically format code you need to do one more thing.
Hit Ctrl + ,
.
Click on the little file icon to open the settings.json
file for the current active profile, your Angular profile.
Also paste this configuration in there.
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"prettier.useEditorConfig": false
}
Now go to any TypeScript, JavaScript, HTML, CSS or JSON file and add a few tabs at a random place before some code.
Hit Ctrl + S
.
You will see how the code is automatically formatted.
🎉
You can also check the Output
for Prettier.
You can view Prettier's output to see if any errors are logged.
To open the Output
panel hit Ctrl + Shift + U
.
From the dropdown, select Prettier
to check for any error messages.
Go to file and hit Ctrl + S
while having the Prettier task Output
panel open.
You should see output similar to this in the panel.
["INFO" - 5:45:31 PM] Formatting file:///angular-prettier-eslint/app/src/app/app.component.ts
["INFO" - 5:45:31 PM] Using config file at /angular-prettier-eslint/app/.prettierrc
["INFO" - 5:45:31 PM] PrettierInstance:
{
"modulePath": "/angular-prettier-eslint/app/node_modules/prettier/index.cjs",
"messageResolvers": {},
"version": "3.3.3"
}
["INFO" - 5:45:31 PM] Using ignore file (if present) at /angular-prettier-eslint/.prettierignore
["INFO" - 5:45:31 PM] File Info:
{
"ignored": false,
"inferredParser": "typescript"
}
["INFO" - 5:45:31 PM] Detected local configuration (i.e. .prettierrc or .editorconfig), VS Code configuration will not be used
["INFO" - 5:45:31 PM] Prettier Options:
{
"filepath": "/angular-prettier-eslint/app/src/app/app.component.ts",
"parser": "typescript",
"useTabs": false,
"tabWidth": 2,
"singleQuote": true,
"semi": true,
"trailingComma": "es5"
}
["INFO" - 5:45:31 PM] Formatting completed in 32ms.
This line is important in the output.
Detected local configuration (i.e. .prettierrc or .editorconfig), VS Code configuration will not be used
This means that the local .prettierrc
file you created for the project is being used to format the code according to the rules in that file.
"scripts": {
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
},
I'll explain each of these script commands in detail:
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
Similar to the first command, but instead of modifying files, it only checks if they are properly formatted:
prettier --check
: Runs Prettier but only checks if files are formatted correctly without making changes- The glob pattern is identical to the one in the
format
command - This is useful in CI/CD pipelines or to verify formatting without changing files
- It will exit with an error code if any files aren't properly formatted
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
This command runs Prettier, a code formatter, to automatically format code files in your Angular project:
prettier --write
: Runs Prettier and writes the formatting changes directly to the files\"{,src/**/}*.{ts,js,html,css,json,md}\"
: This is a glob pattern that defines which files to format:{,src/**/}
means "the root directory and all directories/subdirectories inside src/"*.{ts,js,html,css,json,md}
means "any files with these extensions" (TypeScript, JavaScript, HTML, CSS, JSON, and Markdown)
- When executed, this command will reformat all matching files according to Prettier's rules
Run this command to check if any files need formatting without actually changing them.
npm run format:check
Run this command to format all files. This will modify the files in place. The logic in the command ensures that only files that need formatting are modified.
npm run format
ESLint is used to find and fix problems in your JavaScript code.
ESLint is one of the most popular and powerful linters for JavaScript and TypeScript!
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/schematics
Would you like to add ESLint now? yes
✔ Determining Package Manager
› Using package manager: npm
✔ Searching for compatible package version
› Found compatible package version: @angular-eslint/schematics@19.0.2.
✔ Loading package information from registry
✔ Confirming installation
✔ Installing package
All angular-eslint dependencies have been successfully installed 🎉
Please see https://github.com/angular-eslint/angular-eslint for how to add ESLint configuration to your project.
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.
Please see https://github.com/angular-eslint/angular-eslint for more information.
CREATE eslint.config.js (969 bytes)
UPDATE package.json (1175 bytes)
UPDATE angular.json (2877 bytes)
✔ Packages installed successfully.
This installed https://github.com/angular-eslint/angular-eslint for the app.
https://github.com/angular-eslint/angular-eslint?tab=readme-ov-file#quick-start
If you like to add Angular ESLint to an existing project use this command.
ng add angular-eslint
✔ Determining Package Manager
› Using package manager: npm
✔ Searching for compatible package version
› Found compatible package version: angular-eslint@19.2.1.
✔ Loading package information from registry
✔ Confirming installation
✔ Installing package
All angular-eslint dependencies have been successfully installed 🎉
Please see https://github.com/angular-eslint/angular-eslint for how to add ESLint configuration to your project.
UPDATE package.json (2594 bytes)
Check your /app/package.json file to see what has been installed.
Before
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.3",
"typescript": "~5.5.2",
"typescript-eslint": "8.18.0"
}
}
Here your package.json
after you installed ESLint, that is https://github.com/angular-eslint/angular-eslint.
After
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"angular-eslint": "19.0.2", <=== ESLint is now installed as development dependency
"eslint": "^9.16.0", <=== ESLint is now installed as development dependency
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.3",
"typescript": "~5.5.2",
"typescript-eslint": "8.18.0"
}
}
Now lint the app again.
ng lint
Linting "app"...
All files pass linting
Open this file.
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
@Component({
selector: "app-root",
standalone: true,
imports: [RouterOutlet],
templateUrl: "./app.component.html",
styleUrl: "./app.component.css",
})
export class AppComponent {
title = "app";
}
Change
selector: "app-root",
to this.
selector: "root",
You should immediately see the error being highlighted in VS Code.
Confirm ESLint works correct for you, then reverse the changes.
Change
selector: "root",
back to
selector: "app-root",
again and make sure that the ESLint error is gone.
You successfully added https://github.com/angular-eslint/angular-eslint to the app and are now using ESLint to lint you app.
What does the word lint
mean at all and where does it come from ?
Perhaps this article from WikiPedia gives you a better idea.
https://en.wikipedia.org/wiki/Lint\_(material)
The fun in this is, the word lint
is a NOUN but used as a verb in software development all the time when i.e. a fellow dev asks you oh my gosh, have you at all linted your code
or go lint you code
. 😄
Please follow the links below for the packages you care about.
- This is the core package that exposes most of the other packages below for the common use case of using angular-eslint with Angular CLI workspaces. It exposes all the tooling you need to work with ESLint v9 and typescript-eslint v8 with flat config in v18 of angular-eslint onwards. For versions of angular-eslint older than v18, or workspaces still using ESLint v8 and typescript-eslint v7 or the legacy eslintrc config format, you will use a combination of the packages below directly.
- An Angular CLI Builder which is used to execute ESLint on your Angular projects using standard commands such as ng lint
- An ESLint-specific plugin that contains rules which are specific to Angular projects. It can be combined with any other ESLint plugins in the normal way.
@angular-eslint/eslint-plugin-template
- An ESLint-specific plugin which, when used in conjunction with @angular-eslint/template-parser, allows for Angular template-specific linting rules to run.
- Schematics which are used to add and update configuration files which are relevant for running ESLint on an Angular workspace.
@angular-eslint/template-parser/a>
- An ESLint-specific parser which leverages the @angular/compiler to allow for custom ESLint rules to be written which assert things about your Angular templates.
- Utilities which are helpful when testing custom ESLint rules for Angular workspaces.
- Utilities which are helpful when writing custom ESLint rules for Angular workspaces.
https://eslint.org/blog/2024/04/eslint-v9.0.0-released/
In version 9 of ESLint, the default configuration format has been changed to the so called "flat config" style using exclusively an eslint.config.js
file as the only way of configuring a project.
The legacy eslintrc
style is now deprecated, but still fully supported.
When it comes to configuring ESLint for your Angular projects you have two options and associated guides.
FLAT configs
the default in ESLint v9, strongly recommended for new projects
https://github.com/angular-eslint/angular-eslint/blob/main/docs/CONFIGURING_FLAT_CONFIG.md
eslintrc style configs
the default in ESLint v8, deprecated in ESLint v9 but still valid for existing projects
https://github.com/angular-eslint/angular-eslint/blob/main/docs/CONFIGURING_ESLINTRC.md
The /app/eslint.config.js file currently has this content.
// @ts-check
const eslint = require("@eslint/js");
const tseslint = require("typescript-eslint");
const angular = require("angular-eslint");
module.exports = tseslint.config(
{
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: {},
}
);
This is the so called FLAT ESLint config.
https://github.com/angular-eslint/angular-eslint/blob/main/docs/CONFIGURING_FLAT_CONFIG.md
These days with the flat config, ESLint has first class support for different types of files being configured differently (different rules and parsers)
We can leverage this for Angular projects, because they:
- use TypeScript files for source code
- use a custom/extended form of HTML for templates (be they inline or external HTML files)
The thing is: ESLint understands neither of these things out of the box.
Fortunately, however, ESLint has clearly defined points of extensibility that we can leverage to make this all work.
For detailed information about ESLint plugins, parsers etc please review the official ESLint eslintrc config documentation: https://eslint.org/docs/latest/use/configure/
The key principle of our configuration required for Angular projects is that we need to run different blocks of configuration for different file types/extensions.
In other words, we don't want the same rules to apply on TypeScript files that we do on HTML/inline-templates.
Therefore, our flat config will contain two entries, one for TS, one for HTML.
We could provide these two entries directly in an exported array, but typescript-eslint
provides an awesome typed utility function which makes writing our flat configs a lot nicer.
So we will instead require the function and pass in multiple objects for our configuration.
Copy all the comments into the FLAT ESLint config.
Before
// @ts-check
const eslint = require("@eslint/js");
const tseslint = require("typescript-eslint");
const angular = require("angular-eslint");
module.exports = tseslint.config(
{
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: {},
}
);
After
// @ts-check
// Allows us to bring in the recommended core rules from eslint itself
const eslint = require("@eslint/js");
// Allows us to use the typed utility for our config, and to bring in the recommended rules for TypeScript projects from typescript-eslint
const tseslint = require("typescript-eslint");
// Allows us to bring in the recommended rules for Angular projects from angular-eslint
const angular = require("angular-eslint");
// Export our config array, which is composed together thanks to the typed utility function from typescript-eslint
module.exports = tseslint.config(
{
// Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc)
files: ["**/*.ts"],
extends: [
// Apply the recommended core rules
eslint.configs.recommended,
// Apply the recommended TypeScript rules
...tseslint.configs.recommended,
// Optionally apply stylistic rules from typescript-eslint that improve code consistency
...tseslint.configs.stylistic,
// Apply the recommended Angular rules
...angular.configs.tsRecommended,
],
// Set the custom processor which will allow us to have our inline Component templates extracted
// and treated as if they are HTML files (and therefore have the .html config below applied to them)
processor: angular.processInlineTemplates,
// Override specific rules for TypeScript files (these will take priority over the extended configs above)
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
},
},
{
// Everything in this config object targets our HTML files (external templates,
// and inline templates as long as we have the `processor` set on our TypeScript config above)
files: ["**/*.html"],
extends: [
// Apply the recommended Angular template rules
...angular.configs.templateRecommended,
// Apply the Angular template rules which focus on accessibility of our apps
...angular.configs.templateAccessibility,
],
rules: {},
}
);
https://github.com/angular-eslint/angular-eslint/blob/main/packages/angular-eslint/src/configs/README.md
These flat ESLint configs exist for your convenience.
They contain configuration intended to save you time and effort when configuring your project by disabling rules known to conflict with this repository, or cause issues in Angular codebases.
NOTE: These configs are only compatible with
eslint.config.js
files, not eslintrc.
You should access the configs exported from the
@angular-eslint/eslint-plugin
package for use in eslintrc files.
Quite simply, these enable all the possible rules from @angular-eslint/eslint-plugin
and @angular-eslint/eslint-plugin-template
respectively.
It is unlikely they will be applicable in real-world projects, but some folks find them useful to have as a reference.
The recommended sets from @angular-eslint/eslint-plugin
and @angular-eslint/eslint-plugin-template
are opinionated sets of Angular-specific rules that we think you should use because:
- They help you adhere to Angular best practices.
- They help catch probable issue vectors in your code.
That being said, it is not the only way to use @angular-eslint/eslint-plugin
or @angular-eslint/eslint-plugin-template
, nor is it the way that will necessarily work 100% for your project/company.
It has been built based off of two main things:
-
Angular best practices collected and collated from places like:
- Angular repo.
- Angular documentation.
- Advice from the Angular Team at Google.
-
The combined state of community contributed rulesets at the time of creation.
It is strongly encouraged to combine the recommended Angular rules with the recommended configs from typescript-eslint
(https://typescript-eslint.io/linting/configs/#recommended-configurations), and this is what our schematics will generate for you automatically.
If you disagree with a rule (or it disagrees with your codebase), consider using your local config to change the rule config so it works for your project.
For example, if you want to turn off the @angular-eslint/no-input-rename
TS rule, and turn off the '@angular-eslint/template/no-negated-async' template rule, you can add them to the applicable sections of the config and set them to "off" like this:
eslint.config.js
// @ts-check
const eslint = require("@eslint/js");
const tseslint = require("typescript-eslint");
const angular = require("angular-eslint");
module.exports = tseslint.config(
{
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...angular.configs.tsRecommended,
],
processor: angular.processInlineTemplates,
rules: {
// our project thinks using renaming inputs is ok
"@angular-eslint/no-input-rename": "off",
},
},
{
files: ["**/*.html"],
extends: [
...angular.configs.templateRecommended,
...angular.configs.templateAccessibility,
],
rules: {
// our project thinks using negated async pipes is ok
"@angular-eslint/template/no-negated-async": "off",
},
}
);
These are all the rules within @angular-eslint/eslint-plugin-template
which deal with things impacting the accessibility of your Angular apps.
The rules are based on a number of best practice recommendations and resources including:
If you feel the provided ESLint configuration up to this point are not enough for your project you can continue to add even more configurations for ESLint with regards to Angular.
Instead of meticulously spending days, weeks and maybe months to find a good ESLint configuration this work can be left to experienced industry professionals.
There are two contenders in this space.
AirBnB
https://github.com/airbnb/javascript
Code PushUp
https://github.com/code-pushup/eslint-config
While the AirBnB config is more geared towards JavaScript and React the Code PushUp config is geared particularly for Angular.
Of course your team and you can also configure your own linting rules over the course of a project.
https://github.com/sweepline/eslint-plugin-unused-imports
npm install --save-dev eslint-plugin-unused-imports
https://github.com/import-js/eslint-plugin-import
npm install --save-dev eslint-plugin-import
https://eslint.style/packages/default
npm install --save-dev @stylistic/eslint-plugin
https://typicode.github.io/husky/
Husky enhances your commits and more 🐶 woof!
Automatically lint your commit messages, code, and run tests upon committing or pushing.
npm install --save-dev husky
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint",
"prepare": "husky" <=== add husky install as prepare script
}
npm run prepare
npx husky init
The init command simplifies setting up husky in a project.
It creates a pre-commit script in .husky
and updates the prepare script in /app/package.json.
Modifications can be made later to suit your workflow.
https://typicode.github.io/husky/how-to.html#adding-a-new-hook
You can add a new hook by adding a corresponding file in the .husky
folder of your repository.
For each of these you can create a Husky Hook file, i.e. post-commit
and hook into the exact Git operation that you need.
- applypatch-msg – Check patch message. (e.g., verify format before applying patch)
- pre-applypatch – Run before applying patch. (e.g., run tests)
- post-applypatch – After patch applied. (e.g., notify team)
- pre-commit – Before commit. (e.g., lint code)
- prepare-commit-msg – Edit commit message before editor opens. (e.g., add ticket ID)
- commit-msg – Check message after written. (e.g., enforce message rules)
- post-commit – After commit. (e.g., show summary)
- pre-rebase – Before rebase starts. (e.g., check branch)
- post-checkout – After checkout. (e.g., setup env)
- post-merge – After merge. (e.g., re-install deps)
- pre-push – Before push. (e.g., run tests)
- pre-receive – On remote before accepting push. (e.g., validate code)
- update – On remote for each branch. (e.g., check access)
- post-receive – After push on remote. (e.g., deploy)
- post-update – After update on remote. (e.g., notify services)
- push-to-checkout – During
git push
with--checkout
. (e.g., custom behavior) - pre-auto-gc – Before auto garbage collection. (e.g., log cleanup)
Managing Multiple Git Hooks with Husky
When you have multiple Git hooks, you don't need to manually select which hook to use before committing - Git will automatically run all configured pre-commit hooks in sequence. Here's how it works and how to manage multiple hooks:
Understanding Git Hooks Execution
Git hooks are executed automatically at specific points in your Git workflow. For the pre-commit stage:
- When you run
git commit
, Git automatically checks for and executes all pre-commit hooks - All hooks must succeed (exit with code 0) for the commit to proceed
- If any hook fails, the commit is aborted
The simplest approach is to add multiple commands to your pre-commit hook file:
# .husky/pre-commit
# Run lint-staged
npx lint-staged
# Run additional checks
npm run test:quick
# Run additional checks
npm run type-check
This way, all commands run sequentially when you commit.
Skip Git Hooks
The -n
or --no-verify
flag skips Git hooks like pre-commit
and commit-msg
.
# Skips Git hooks
git commit -m "..." -n
https://typicode.github.io/husky/how-to.html#project-not-in-git-root-directory
In the case of this repository the /app
folder is where Husky is installed as that is the location where the package.json
file is, so this is the project folder.
However Git is in the root folder.
In this case prepare Husky so that Git is run from the root folder..
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint",
"prepare": "cd .. && husky app/.husky" <=== move to root directory and tell husky where the .husky folder is
}
.. and in the pre-commit
hook change the folder back to where the app is.
# app/.husky/pre-commit
cd app
npm test
exit 1
This is just needed in this demo repository where an Angular app is in app/
.
https://typicode.github.io/husky/how-to.html#testing-hooks-without-committing
By adding exit 1
to the end of the pre-commit
hook the Git commit is aborted, so you can test your hooks like this.
https://github.com/lint-staged/lint-staged
🚫💩 — Run tasks like formatters and linters against staged git files
requires further setup after npm install
npm install --save-dev lint-staged
Create the lint-staged Configuration
Create a lint-staged.config.js
file in the root of the application project.
https://github.com/lint-staged/lint-staged/blob/main/lint-staged.config.js
/** @type {import('./lib/types').Configuration} */
export default {
// Run ESLint with auto-fix on all JavaScript files
// This checks for and fixes code quality issues according to ESLint rules
"*.js": "eslint --fix",
// Run Prettier formatter on JavaScript, CSS, and Markdown files
// This ensures consistent code formatting across these file types
"*.{js,css,md}": "prettier --write",
// For TypeScript files, run two commands in sequence:
// 1. ESLint to check and fix linting issues
// 2. TypeScript compiler in strict mode to verify types without generating output files
"*.ts": ["eslint --fix", "tsc --noEmit --strict"],
};
Create the lint-staged Commands
Adjust the pre-commit
hook so that npx lint-staged
runs as part of it.
# app/.husky/pre-commit
# always cd into app in this repository because Git is not at the root of the repository
cd app
# Run lint-staged to check and format staged files
npx lint-staged
# Uncomment to run tests before commit
# npm test
# https://github.com/lint-staged/lint-staged/blob/main/.husky/pre-commit
# node bin/lint-staged.js
# Comment below line to commit the commit if all commands have exited with 0
exit 1
Important
For ng lint
or npx lint-staged
to be able to run either rename eslint.config.js
to eslint.config.cjs
OR convert the require
syntax to import
syntax and add "type":"module"
to /app/package.json
Before
// @ts-check
// Allows us to bring in the recommended core rules from eslint itself
const eslint = require("@eslint/js");
// Allows us to use the typed utility for our config, and to bring in the recommended rules for TypeScript projects from typescript-eslint
const tseslint = require("typescript-eslint");
// Allows us to bring in the recommended rules for Angular projects from angular-eslint
const angular = require("angular-eslint");
// Export our config array, which is composed together thanks to the typed utility function from typescript-eslint
module.exports = tseslint.config(
{
// Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc)
files: ["**/*.ts"],
extends: [
// Apply the recommended core rules
eslint.configs.recommended,
// Apply the recommended TypeScript rules
...tseslint.configs.recommended,
// Optionally apply stylistic rules from typescript-eslint that improve code consistency
...tseslint.configs.stylistic,
// Apply the recommended Angular rules
...angular.configs.tsRecommended,
],
// Set the custom processor which will allow us to have our inline Component templates extracted
// and treated as if they are HTML files (and therefore have the .html config below applied to them)
processor: angular.processInlineTemplates,
// Override specific rules for TypeScript files (these will take priority over the extended configs above)
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
},
},
{
// Everything in this config object targets our HTML files (external templates,
// and inline templates as long as we have the `processor` set on our TypeScript config above)
files: ["**/*.html"],
extends: [
// Apply the recommended Angular template rules
...angular.configs.templateRecommended,
// Apply the Angular template rules which focus on accessibility of our apps
...angular.configs.templateAccessibility,
],
rules: {},
}
);
After
// @ts-check
// Allows us to bring in the recommended core rules from eslint itself
import eslint from "@eslint/js"; // <=== use import
// Allows us to use the typed utility for our config, and to bring in the recommended rules for TypeScript projects from typescript-eslint
import * as tseslint from "typescript-eslint"; // <=== use import
// Allows us to bring in the recommended rules for Angular projects from angular-eslint
import * as angular from "angular-eslint"; // <=== use import
// Export our config array, which is composed together thanks to the typed utility function from typescript-eslint
export default tseslint.config(
{
// Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc)
files: ["**/*.ts"],
extends: [
// Apply the recommended core rules
eslint.configs.recommended,
// Apply the recommended TypeScript rules
...tseslint.configs.recommended,
// Optionally apply stylistic rules from typescript-eslint that improve code consistency
...tseslint.configs.stylistic,
// Apply the recommended Angular rules
...angular.configs.tsRecommended,
],
// Set the custom processor which will allow us to have our inline Component templates extracted
// and treated as if they are HTML files (and therefore have the .html config below applied to them)
processor: angular.processInlineTemplates,
// Override specific rules for TypeScript files (these will take priority over the extended configs above)
rules: {
"@angular-eslint/directive-selector": [
"error",
{
type: "attribute",
prefix: "app",
style: "camelCase",
},
],
"@angular-eslint/component-selector": [
"error",
{
type: "element",
prefix: "app",
style: "kebab-case",
},
],
},
},
{
// Everything in this config object targets our HTML files (external templates,
// and inline templates as long as we have the `processor` set on our TypeScript config above)
files: ["**/*.html"],
extends: [
// Apply the recommended Angular template rules
...angular.configs.templateRecommended,
// Apply the Angular template rules which focus on accessibility of our apps
...angular.configs.templateAccessibility,
],
rules: {},
}
);
Before
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint",
"prepare": "cd .. && husky app/.husky",
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"lint:check": "npx eslint \"{,src/**/}*.{ts,html,js}\"",
"lint:all": "npx eslint \"{,src/**/}*.{ts,html,js}\" --fix",
"fix": "npm run format; npm run lint:all"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"angular-eslint": "19.0.2",
"eslint": "^9.16.0",
"husky": "^9.1.7",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"lint-staged": "^15.5.0",
"prettier": "^3.3.3",
"typescript": "~5.5.2",
"typescript-eslint": "8.18.0"
}
}
After
{
"name": "app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint",
"prepare": "cd .. && husky app/.husky",
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"lint:check": "npx eslint \"{,src/**/}*.{ts,html,js}\"",
"lint:all": "npx eslint \"{,src/**/}*.{ts,html,js}\" --fix",
"fix": "npm run format; npm run lint:all"
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.5",
"@angular/cli": "^18.2.5",
"@angular/compiler-cli": "^18.2.0",
"@types/jasmine": "~5.1.0",
"angular-eslint": "19.0.2",
"eslint": "^9.16.0",
"husky": "^9.1.7",
"jasmine-core": "~5.2.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"lint-staged": "^15.5.0",
"prettier": "^3.3.3",
"typescript": "~5.5.2",
"typescript-eslint": "8.18.0"
},
"type": "module" <=== this is added here, then eslint.config.js can run with modern imports
}
You are now ready to try out Husky and lint-staged.
Now run
git add -A
to stage your changed files and then run
git commit -m "pre-commit hook test, husky, lint-staged"
Instead of saving your work in the commit this is shown on the terminal.
✔ Backed up original state in git stash (31f52fe)
✔ Hiding unstaged changes to partially staged files...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Restoring unstaged changes to partially staged files...
✔ Cleaning up temporary files...
🎉 Congratulations, your husky - lint-staged pre-commit hook runs fine. ❤️
Now go to your pre-commit
file and comment out the last line.
# app/.husky/pre-commit
# always cd into app in this repository because Git is not at the root of the repository
cd app
# Run lint-staged to check and format staged files
npx lint-staged
# Uncomment to run tests before commit
# npm test
# https://github.com/lint-staged/lint-staged/blob/main/.husky/pre-commit
# node bin/lint-staged.js
# Comment below line to commit the commit if all commands have exited with 0
# exit 1 # <=== comment out this line, if all processes exit with 0 then the commit is saved
Now, if all processes that are configured to run by npx lint-staged
in the /app/lint-staged.config.js file exit with code 0
, then the latest work is saved in the commit and Prettier and ESLint checked and repaired all the staged files.
git commit -m "pre-commit hook, husky, lint-staged, prettier, eslint"
First the pre-commit hook processes run..
✔ Backed up original state in git stash (6961bf4)
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
..then the normal git hook pre-commit is finished running and the staged files are saved in a new commit.
[husky-lint-staged 8a9ad02] pre-commit hook, husky, lint-staged, prettier, eslint
10 files changed, 713 insertions(+), 91 deletions(-)
create mode 100644 app/lint-staged.config.js
Change the on-demand npm script so that they have a more detailed scope for the project.
"scripts": {
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
"lint:check": "npx eslint \"{,src/**/}*.{ts,html,js}\"",
"lint:all": "npx eslint \"{,src/**/}*.{ts,html,js}\" --fix",
"fix": "npm run format; npm run lint:all"
},
I'll explain each of these script commands in detail:
"format:check": "prettier --check \"{,src/**/}*.{ts,js,html,css,json,md}\"",
Similar to the first command, but instead of modifying files, it only checks if they are properly formatted:
prettier --check
: Runs Prettier but only checks if files are formatted correctly without making changes- The glob pattern is identical to the one in the
format
command - This is useful in CI/CD pipelines or to verify formatting without changing files
- It will exit with an error code if any files aren't properly formatted
"format": "prettier --write \"{,src/**/}*.{ts,js,html,css,json,md}\"",
This command runs Prettier, a code formatter, to automatically format code files in your Angular project:
prettier --write
: Runs Prettier and writes the formatting changes directly to the files\"{,src/**/}*.{ts,js,html,css,json,md}\"
: This is a glob pattern that defines which files to format:{,src/**/}
means "the root directory and all directories/subdirectories inside src/"*.{ts,js,html,css,json,md}
means "any files with these extensions" (TypeScript, JavaScript, HTML, CSS, JSON, and Markdown)
- When executed, this command will reformat all matching files according to Prettier's rules
"lint:check": "npx eslint \"{,src/**/}*.{ts,html,js}\"",
This command runs ESLint to check for code quality issues without automatically fixing them:
-
npx eslint
: Runs ESLint from your node_modules -
\"{,src/**/}*.{ts,html,js}\"
: Similar glob pattern to other commands:{,src/**/}
means "the root directory and all directories/subdirectories inside src/"*.{ts,html,js}
means "any files with these extensions" (TypeScript, HTML, and JavaScript)
-
Unlike
lint:all
, this command omits the--fix
flag, so it only reports issues without attempting to fix them -
This is particularly useful for:
- Code review processes where you want to see all issues before fixing
- CI/CD pipelines where you want to fail the build if there are linting errors
- Local development when you want to review issues before applying fixes
"lint:all": "npx eslint \"{,src/**/}*.{ts,html,js}\" --fix",
This command runs ESLint to check for code quality issues and potential bugs:
npx eslint
: Runs ESLint from your node_modules\"{,src/**/}*.{ts,html,js}\"
: Similar glob pattern to the Prettier commands, but only targeting TypeScript, HTML, and JavaScript files--fix
: Tells ESLint to automatically fix problems where possible- ESLint focuses on code quality and potential issues beyond just formatting (bugs, potential errors, coding standards)
These scripts are complementary and serve different purposes:
format
ensures your code looks consistent (spacing, quotes, etc.)format:check
verifies formatting without making changeslint:all
checks for code quality issues and can fix many of them automatically
These commands are typically part of a development workflow for maintaining code quality in an Angular project, and you might run them before committing code or as part of your CI/CD pipeline.
/** @type {import('./lib/types').Configuration} */
export default {
// Run ESLint with auto-fix on all TypeScript, HTML, and JavaScript files
// This checks for and fixes code quality issues according to ESLint rules
// Matches the 'lint:all' script from package.json
"*.{ts,html,js}": "eslint --fix",
// Run Prettier formatter on TypeScript, JavaScript, HTML, CSS, JSON, and Markdown files
// This ensures consistent code formatting across all source files
// Matches the 'format' script from package.json
"*.{ts,js,html,css,json,md}": "prettier --write",
// For TypeScript files, run additional type checking:
// TypeScript compiler in strict mode to verify types without generating output files
// This provides an extra layer of type safety for staged TypeScript files
"*.ts": ["tsc --noEmit --strict"],
};