Angular 19 Development Setup
Practical Angular 19 setup: CLI and workspace configuration, TypeScript tips, ESLint and Prettier integration, plus commit-hook examples to maintain a robust workflow.
Introduction
This guide provides a practical, up-to-date walkthrough to set up a modern development environment with Angular 19. It is intended for teams migrating from older versions and for developers starting a new project who want to apply best practices from day one.
We cover CLI and project setup using Standalone Components, the Signals-based reactivity model, and essential tooling for code quality (ESLint, Prettier) and workflow integration (Husky, lint-staged). The guide also includes recommendations on folder structure, testing, accessibility, and production optimizations.
By the end of this article you will have a reference project ready for production: configured for a great developer experience, with robust linting and formatting rules, local CI hooks, and scaffolding that supports maintainability and scalability.
TL;DR
Quick summary
- Create a new Angular 19 project with Standalone Components.
- Use ESLint (flat config) + Prettier and hook them with Husky + lint-staged.
- Prefer Signals and the new `input()`/`output()` helpers for modern patterns.
- Verify with `pnpm exec tsc --noEmit`, `pnpm exec eslint` and `pnpm run format:check`.
- Angular CLI 19 installation
- Creating a new Standalone Components project
- Signals-based architecture
- ESLint configuration for Angular 19
- Prettier configuration for Angular templates
- Husky and lint-staged integration
- Standalone Components by default (no NgModules)
- Signals-based reactivity
- Improved performance and bundle size
- New
input()andoutput()functions - Simplified project structure
Prerequisites
Before you begin, make sure you have:
- Node.js (v18.19+; v20.x LTS recommended) — Angular 19 requires at least Node 18.19
- npm (included with Node.js) — v9.x or higher
- A code editor (VSCode recommended)
Verify versions
1node --version # Should be v18.19+ or v20.x+
2npm --version # Should be 9.x or higherQuick verification
This repository uses pnpm. If you prefer npm or yarn, equivalents are shown.
1pnpm install # (npm) npm install
2pnpm dev # (npm) npm run dev | (yarn) yarn dev
3pnpm exec tsc --noEmit # typecheck
4pnpm exec eslint "app/**/*.{ts,tsx,js,jsx}" # lint
5pnpm exec prettier --check "src/**/*.{ts,tsx,html,scss,json}" # format checkStep 1 — Install Angular CLI 19
Install Globally
1npm install -g @angular/cli@latestVerify Installation
1ng versionExpected output:
1Angular CLI: 19.x.x
2Node: 20.x.x (or 18.19+)
3Package Manager: npm 10.x.x
4OS: win32 x64Important Version Notes
- Angular 19 requires Node.js 18.19+ (recommended: 20.x LTS)
- npm (included with Node.js) — v9.x or higher
- Uses Standalone Components by default
Step 2 — Create a New Angular 19 Project
Create Project with Standalone Components
1ng new my-angular-appConfiguration Options (Angular 19)
You will be asked several questions:
1. Which stylesheet format would you like to use?
1? Which stylesheet format would you like to use?
2CSS
3❯ SCSS [ https://sass-lang.com/documentation/syntax#scss ]
4Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]
5Less [ http://lesscss.org ]Recommendation: Select SCSS for modern styling
2. Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)?
1? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? (y/N)- Yes: For better SEO and initial load performance
- No: For client-side only applications
Note: Angular 19 NO longer asks about routing - routing is included by default via provideRouter() in app.config.ts.
Navigate to Project
1cd my-angular-appTest the Installation
1ng serveOpen browser at http://localhost:4200 - you should see the Angular welcome page.
1ng serve --open # Opens browser automatically
2ng serve --port 4300 # Use different port
3ng serve --ssl # Enable HTTPSAngular 19 Project Structure (Standalone)
Key Difference: No more app.module.ts - Angular 19 uses Standalone Components by default!
1my-angular-app/
2├── public/
3│ └── favicon.ico # Favicon
4├── src/
5│ ├── app/
6│ │ ├── app.component.ts # Root standalone component
7│ │ ├── app.component.html # Root template
8│ │ ├── app.component.scss # Root styles
9│ │ ├── app.component.spec.ts # Unit tests
10│ │ ├── app.config.ts # App configuration (replaces app.module.ts)
11│ │ └── app.routes.ts # Routes configuration
12│ ├── index.html # Main HTML
13│ ├── main.ts # App entry point (uses bootstrapApplication)
14│ └── styles.scss # Global styles
15├── angular.json # Angular CLI config
16├── package.json # Dependencies
17├── package-lock.json # Locked dependencies
18├── tsconfig.json # TypeScript base config
19├── tsconfig.app.json # App-specific TypeScript config
20├── tsconfig.spec.json # Test-specific TypeScript config
21└── README.md # Project documentationNote: Angular 19 uses public/ folder for static files (like favicon) instead of the old src/assets/ approach.
Key Files Explained
main.ts - Bootstrap with Standalone:
1bootstrapApplication(AppComponent, appConfig).catch((err) =>
2 console.error(err)
3);app.config.ts - Application Configuration:
1export const appConfig: ApplicationConfig = {
2 providers: [provideRouter(routes)],
3};app.component.ts - Standalone Component:
1@Component({
2 selector: "app-root",
3 standalone: true, // ← Standalone component
4 imports: [RouterOutlet], // ← Import dependencies directly
5 templateUrl: "./app.component.html",
6 styleUrl: "./app.component.scss",
7})
8export class AppComponent {
9 title = "my-angular-app";
10}app.routes.ts - Routes Configuration:
1export const routes: Routes = [];Configure ESLint for Angular 19
ESLint is a widely used open-source static code analysis tool for identifying and fixing problems in JavaScript, TypeScript, and related languages. It helps developers maintain code quality and consistency by enforcing coding standards, detecting syntax errors, and highlighting potential bugs or anti-patterns before code is executed. ESLint is highly configurable and supports custom rules, plugins, and integrations with modern frameworks like Angular, React, and Vue. In Angular projects, ESLint can also validate HTML templates and enforce Angular-specific best practices, making it an essential tool for scalable, maintainable, and secure codebases.
Why Angular Needs Specific ESLint?
- Validation of HTML templates (directives, pipes, binding)
- Rules for Standalone Components
- Validation of Signals and new reactive primitives
- Rules for new
input()andoutput()functions - Detection of Angular anti-patterns
Difference with Generic ESLint
| Feature | Generic ESLint | Angular 19 ESLint |
|---|---|---|
| Validates TypeScript | ||
| Validates HTML templates | ||
| Standalone component rules | ||
| Signals validation | ||
| Modern input/output functions |
Install — Angular ESLint
1ng add @angular-eslintWhat does this command do?
- Installs
angular-eslintpackage (v20.5.1) - all-in-one package - Installs ESLint 9.x (v9.38.0+) - latest with flat config
- Installs
typescript-eslint(v8.46.2+) - Creates
eslint.config.jswith flat config format (ESLint 9+) - Updates
angular.jsonwith ESLint builder - Adds
lintscript topackage.json - Includes rules for Standalone Components and Signals
Installed packages:
angular-eslint: 20.5.1 (includes builder, plugin, template parser, and schematics)eslint: ^9.38.0typescript-eslint: 8.46.2typescript: ~5.7.2
Note: angular-eslint is now a unified package that includes all necessary Angular ESLint components.
Verify Installation
1ng lintShould show: "All files pass linting" or list of errors to fix.
Project Structure After ESLint Installation
After running ng add @angular-eslint, your project structure will look like this:
1my-angular-app/
2├── public/
3│ └── favicon.ico
4├── src/
5│ ├── app/
6│ │ ├── app.component.ts
7│ │ ├── app.component.html
8│ │ ├── app.component.scss
9│ │ ├── app.component.spec.ts
10│ │ ├── app.config.ts
11│ │ └── app.routes.ts
12│ ├── index.html
13│ ├── main.ts
14│ └── styles.scss
15├── angular.json # ← Updated with ESLint builder
16├── eslint.config.js # ← NEW: ESLint 9 flat config
17├── package.json # ← Updated with ESLint dependencies
18├── package-lock.json # ← Updated
19├── tsconfig.json
20├── tsconfig.app.json
21├── tsconfig.spec.json
22└── README.mdFiles created/modified:
eslint.config.js- New ESLint 9 flat configuration fileangular.json- Updated with@angular-eslint/builder:lintpackage.json- New dependencies and scripts added- angular-eslint (v20.5.1)
- eslint (v^9.38.0)
- typescript-eslint (v8.46.2)
- lint script added to scripts section
ESLint 9 Flat Config Format (Angular 19)
Important: Angular ESLint 20.x uses ESLint 9 with the new flat config format (eslint.config.js), replacing the old .eslintrc.json format.
Configuration — Generated eslint.config.js
The ng add command creates a flat config file optimized for Angular 19:
1// @ts-check
2
3module.exports = tseslint.config(
4 {
5 files: ["**/*.ts"],
6 extends: [
7 eslint.configs.recommended,
8 ...tseslint.configs.recommended,
9 ...tseslint.configs.stylistic,
10 ...angular.configs.tsRecommended,
11 ],
12 processor: angular.processInlineTemplates,
13 rules: {
14 "@angular-eslint/directive-selector": [
15 "error",
16 {
17 type: "attribute",
18 prefix: "app",
19 style: "camelCase",
20 },
21 ],
22 "@angular-eslint/component-selector": [
23 "error",
24 {
25 type: "element",
26 prefix: "app",
27 style: "kebab-case",
28 },
29 ],
30 },
31 },
32 {
33 files: ["**/*.html"],
34 extends: [
35 ...angular.configs.templateRecommended,
36 ...angular.configs.templateAccessibility,
37 ],
38 rules: {},
39 }
40);Rules — Key Differences: Flat Config vs Old Format
| Aspect | Old Format (.eslintrc.json) | New Format (eslint.config.js) |
|---|---|---|
| File name | .eslintrc.json | eslint.config.js |
| ESLint version | ESLint 8.x | ESLint 9.x |
| Format | JSON with overrides | JavaScript with flat arrays |
| Config structure | Nested overrides | Flat array of config objects |
| Extending configs | extends array | Spread operator ...configs |
| TypeScript support | Separate parser config | Built-in with typescript-eslint |
| Flexibility | Limited | Full JavaScript power |
Configuration — Flat Config Structure Explained
1. TypeScript Files Configuration:
1{
2 files: ["**/*.ts"], // ← Glob pattern for TypeScript files
3 extends: [
4 eslint.configs.recommended, // ← Base ESLint rules
5 ...tseslint.configs.recommended, // ← TypeScript rules
6 ...tseslint.configs.stylistic, // ← TypeScript style rules
7 ...angular.configs.tsRecommended, // ← Angular-specific rules
8 ],
9 processor: angular.processInlineTemplates, // ← Process inline templates
10 rules: {
11 // Custom rules here
12 },
13}2. HTML Template Files Configuration:
1{
2 files: ["**/*.html"], // ← Glob pattern for HTML templates
3 extends: [
4 ...angular.configs.templateRecommended, // ← Template rules
5 ...angular.configs.templateAccessibility, // ← Accessibility rules
6 ],
7 rules: {
8 // Custom template rules here
9 },
10}New in Angular 19:
@angular-eslint/prefer-standalone- Encourages standalone components- Better support for Signals and reactive primitives
- Improved template accessibility rules
Understanding angular.json ESLint Configuration
After running ng add @angular-eslint/schematics, your angular.json is updated with:
1{
2 "projects": {
3 "my-angular-app": {
4 "architect": {
5 "lint": {
6 "builder": "@angular-eslint/builder:lint",
7 "options": {
8 "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
9 }
10 }
11 }
12 }
13 }
14}Configuration explained:
builder: Uses Angular ESLint builder instead of the old TSLint builderlintFilePatterns: Defines which files to lint (all.tsand.htmlfiles insrc/)
Running the linter:
1npm run lint
2# or
3ng lintConfiguration — Detailed Flat Config Explanation
What is Included in Each Config
- eslint.configs.recommended: Core ESLint rules (no-unused-vars, no-undef, etc.)
- tseslint.configs.recommended: TypeScript-specific rules, type-aware linting
- tseslint.configs.stylistic: Code style preferences, naming conventions
- angular.configs.tsRecommended: Angular selector validation, lifecycle method rules, Standalone component preferences
- angular.configs.templateRecommended: Template syntax validation, binding syntax, structural directive usage
- angular.configs.templateAccessibility: Accessibility rules (ARIA, alt text, labels)
Selector Rules in Flat Config
Directives:
1"@angular-eslint/directive-selector": [
2 "error",
3 {
4 type: "attribute", // ← Directives are attributes
5 prefix: "app", // ← Your app prefix
6 style: "camelCase" // ← Style: appMyDirective
7 }
8],Example:
1// Correct
2@Directive({
3 selector: '[appHighlight]',
4 standalone: true
5})
6
7// Incorrect
8@Directive({
9 selector: '[highlight]' // Missing 'app' prefix
10})Components:
1"@angular-eslint/component-selector": [
2 "error",
3 {
4 type: "element", // ← Components are elements
5 prefix: "app", // ← Your app prefix
6 style: "kebab-case" // ← Style: app-my-component
7 }
8],Example (Angular 19 Standalone):
1// Correct - Standalone component
2@Component({
3 selector: "app-user-card",
4 standalone: true,
5 imports: [CommonModule],
6})
7export class UserCardComponent {}
8
9// Incorrect
10@Component({
11 selector: "userCard", // Doesn't use kebab-case or prefix
12})
13export class UserCardComponent {}Rules — Useful Angular 19 Rules (Flat Config)
Adding Custom Rules
You can add custom rules to your eslint.config.js:
1module.exports = tseslint.config(
2 {
3 files: ["**/*.ts"],
4 extends: [
5 eslint.configs.recommended,
6 ...tseslint.configs.recommended,
7 ...tseslint.configs.stylistic,
8 ...angular.configs.tsRecommended,
9 ],
10 processor: angular.processInlineTemplates,
11 rules: {
12 "@angular-eslint/directive-selector": [
13 "error",
14 {
15 type: "attribute",
16 prefix: "app",
17 style: "camelCase",
18 },
19 ],
20 "@angular-eslint/component-selector": [
21 "error",
22 {
23 type: "element",
24 prefix: "app",
25 style: "kebab-case",
26 },
27 ],
28 "@angular-eslint/no-output-on-prefix": "error",
29 "@angular-eslint/no-output-native": "error",
30 "@angular-eslint/no-input-rename": "error",
31 "@angular-eslint/use-lifecycle-interface": "error",
32 "@angular-eslint/prefer-on-push-component-change-detection": "warn",
33
34 // TypeScript Rules
35 "@typescript-eslint/no-explicit-any": "warn",
36 "@typescript-eslint/explicit-function-return-type": "error",
37 "@typescript-eslint/explicit-module-boundary-types": "error",
38 "@typescript-eslint/explicit-member-accessibility": [
39 "error",
40 {
41 accessibility: "explicit",
42 overrides: {
43 constructors: "no-public",
44 },
45 },
46 ],
47 "no-console": "warn",
48
49 // Import ordering
50 "sort-imports": [
51 "error",
52 {
53 ignoreCase: false,
54 ignoreDeclarationSort: true,
55 ignoreMemberSort: false,
56 memberSyntaxSortOrder: ["none", "all", "multiple", "single"],
57 allowSeparatedGroups: true,
58 },
59 ],
60
61 // Naming Convention Rules
62 "@typescript-eslint/naming-convention": [
63 "error",
64 {
65 selector: "variable",
66 format: ["camelCase", "UPPER_CASE"],
67 },
68 {
69 selector: "property",
70 format: ["camelCase"],
71 },
72 {
73 selector: "method",
74 format: ["camelCase"],
75 },
76 {
77 selector: "typeLike",
78 format: ["PascalCase"],
79 },
80 ],
81 },
82 },
83 {
84 files: ["**/*.html"],
85 extends: [
86 ...angular.configs.templateRecommended,
87 ...angular.configs.templateAccessibility,
88 ],
89 rules: {
90 // Template Rules
91 "@angular-eslint/template/no-negated-async": "error",
92 "@angular-eslint/template/eqeqeq": "error",
93 "@angular-eslint/template/banana-in-box": "error",
94 "@angular-eslint/template/accessibility-alt-text": "warn",
95 "@angular-eslint/template/accessibility-label-for": "warn",
96 "@angular-eslint/template/click-events-have-key-events": "warn",
97 },
98 }
99);Rule Examples Explained
prefer-signals: Recommend using Signals over traditional observables
1// Warning - Old approach
2export class UserComponent {
3 userName = "";
4 @Input() title = "";
5}
6
7// Better - Using Signals (Angular 19)
8export class UserComponent {
9 userName = signal("");
10 title = input<string>(""); // New input() function
11}no-output-on-prefix: Do not use "on" prefix in Outputs
1// Incorrect - Using decorator
2@Output() onClick = new EventEmitter();
3
4// Correct - Using new output() function (Angular 19)
5userClick = output<User>(); // New output() functionuse-lifecycle-interface: Implement lifecycle interfaces
1// Incorrect
2export class MyComponent {
3 ngOnInit() {}
4}
5
6// Correct
7export class MyComponent implements OnInit {
8 ngOnInit() {}
9}prefer-on-push-component-change-detection: Recommends OnPush
1// Warning
2@Component({
3 selector: 'app-my-component',
4 standalone: true,
5 templateUrl: './my-component.html'
6})
7
8// Better performance with Signals
9@Component({
10 selector: 'app-my-component',
11 standalone: true,
12 templateUrl: './my-component.html',
13 changeDetection: ChangeDetectionStrategy.OnPush // Works great with Signals
14})HTML Templates
1{
2 "@angular-eslint/template/no-negated-async": "error",
3 "@angular-eslint/template/eqeqeq": "error",
4 "@angular-eslint/template/banana-in-box": "error",
5 "@angular-eslint/template/accessibility-alt-text": "warn"
6}Explanation:
no-negated-async: Do not negate async pipes
1<!-- Incorrect -->
2<div *ngIf="!(user$ | async)">No user</div>
3
4<!-- Correct -->
5<div *ngIf="(user$ | async) === null">No user</div>eqeqeq: Use === in templates
1<!-- Incorrect -->
2<div *ngIf="status == 'active'">Active</div>
3
4<!-- Correct -->
5<div *ngIf="status === 'active'">Active</div>banana-in-box: Detects common ngModel error
1<!-- Incorrect (inverted parentheses) -->
2<input ([ngModel])="name" />
3
4<!-- Correct -->
5<input [(ngModel)]="name" />accessibility-alt-text: Requires alt on images
1<!-- Incorrect -->
2<img [src]="userAvatar" />
3
4<!-- Correct -->
5<img [src]="userAvatar" [alt]="userName" />Step 4 — Configure Prettier for Angular
Prettier is an opinionated code formatter that enforces a consistent style across your codebase. It supports TypeScript, HTML, CSS/SCSS, and JSON files, making it ideal for Angular projects. By integrating Prettier with ESLint, you can ensure that your code is not only free of linting errors but also consistently formatted according to your preferences. In this step, we will set up Prettier in your Angular 19 project and configure it to work seamlessly with the new ESLint flat config.
Why Prettier for Angular?
- TypeScript files (
.ts) - HTML templates (
.html) - SCSS/CSS files (
.scss,.css) - JSON configuration files
Install Prettier
1npm install --save-dev prettier eslint-config-prettierCreate .prettierrc Configuration
Create .prettierrc at project root:
1{
2 "semi": true,
3 "singleQuote": true,
4 "tabWidth": 2,
5 "useTabs": false,
6 "trailingComma": "es5",
7 "printWidth": 100,
8 "arrowParens": "always",
9 "endOfLine": "lf",
10 "bracketSpacing": true,
11 "bracketSameLine": false,
12 "htmlWhitespaceSensitivity": "css",
13 "overrides": [
14 {
15 "files": "*.html",
16 "options": {
17 "parser": "angular",
18 "printWidth": 120
19 }
20 },
21 {
22 "files": "*.component.html",
23 "options": {
24 "parser": "angular"
25 }
26 }
27 ]
28}Prettier Configuration Explained
Angular-Specific Options- htmlWhitespaceSensitivity: "css"
- Respects CSS
displayproperty for whitespace - Better for Angular templates with inline styles
- Respects CSS
- Override for HTML Templates:
1 { 2 "files": "*.html", 3 "options": { 4 "parser": "angular", 5 "printWidth": 120 6 } 7} - Why
printWidth: 120for HTML?- Angular templates often have long attribute bindings
- 120 chars prevents excessive line breaks
Examples
TypeScript files (.ts) - Angular 19 with Signals:1import { Component, signal, input, output } from "@angular/core";
2
3@Component({
4 selector: "app-user",
5 standalone: true,
6 templateUrl: "./user.component.html",
7})
8export class UserComponent {
9 userName = signal("John Doe");
10 userId = input.required<string>();
11 userSelected = output<string>();
12
13 selectUser() {
14 this.userSelected.emit(this.userId());
15 }
16}.html) - Angular 19 Syntax:1<!-- Uses Angular parser with printWidth: 120 -->
2@if (user(); as user) {
3<div
4 class="user-card"
5 [class.active]="user.isActive"
6 (click)="selectUser(user)"
7>
8 <h2>{{ user.name }}</h2>
9 <p>{{ user.email }}</p>
10</div>
11}
12
13<!-- Old syntax still supported -->
14<div class="user-card" *ngIf="user$ | async as user">
15 <h2>{{ user.name }}</h2>
16</div>Create .prettierignore
1# Dependencies
2node_modules/
3
4# Build outputs
5dist/
6.angular/
7coverage/
8
9# Cache
10.cache/
11.vscode/
12
13# Logs
14*.logIntegration — Integrate Prettier with ESLint Flat Config
With ESLint 9 flat config, Prettier integration is simpler. Install the plugin:
1npm install --save-dev eslint-config-prettierUpdate your eslint.config.js to include Prettier:
1// @ts-check
2
3module.exports = tseslint.config(
4 {
5 files: ["**/*.ts"],
6 extends: [
7 eslint.configs.recommended,
8 ...tseslint.configs.recommended,
9 ...tseslint.configs.stylistic,
10 ...angular.configs.tsRecommended,
11 prettier, // ← Disable conflicting rules with Prettier
12 ],
13 processor: angular.processInlineTemplates,
14 rules: {
15 // Your custom rules
16 },
17 },
18 {
19 files: ["**/*.html"],
20 extends: [
21 ...angular.configs.templateRecommended,
22 ...angular.configs.templateAccessibility,
23 prettier, // ← Disable conflicting rules for templates too
24 ],
25 rules: {},
26 }
27);Important: Add prettier at the end of the extends array to ensure it disables any conflicting ESLint rules.
VSCode Settings for Angular
Create or update .vscode/settings.json:
1{
2 "editor.defaultFormatter": "esbenp.prettier-vscode",
3 "editor.formatOnSave": true,
4 "editor.codeActionsOnSave": {
5 "source.fixAll.eslint": "explicit"
6 },
7 "[typescript]": {
8 "editor.defaultFormatter": "esbenp.prettier-vscode"
9 },
10 "[html]": {
11 "editor.defaultFormatter": "esbenp.prettier-vscode"
12 },
13 "[scss]": {
14 "editor.defaultFormatter": "esbenp.prettier-vscode"
15 },
16 "[json]": {
17 "editor.defaultFormatter": "esbenp.prettier-vscode"
18 }
19}Add Scripts to package.json
1{
2 "scripts": {
3 "ng": "ng",
4 "start": "ng serve",
5 "build": "ng build",
6 "test": "ng test",
7 "lint": "ng lint",
8 "lint:fix": "ng lint --fix",
9 "format": "prettier --write \"src/**/*.{ts,html,scss,json}\"",
10 "format:check": "prettier --check \"src/**/*.{ts,html,scss,json}\"
11 }
12}Test Prettier
Format all files:1npm run format1npm run format:checkStep 5 — Setup Husky and lint-staged
Husky and lint-staged allow you to run ESLint and Prettier on staged files before they are committed. This ensures that only linted and formatted code is committed to your repository, improving code quality and consistency across your Angular project. In this step, we will set up Husky to create Git hooks and lint-staged to run ESLint and Prettier on the files you are trying to commit.
Why Husky and lint-staged for Angular?
- Run ESLint only on staged files (fast)
- Format code with Prettier before commit
- Prevent bad code from entering the repository
- Ensure consistent code quality across the team
Install Dependencies
1npm install --save-dev husky lint-stagedInitialize Husky
1npx husky initConfigure lint-staged
Add to package.json:
1{
2"lint-staged": {
3 "*.ts": ["eslint --fix --max-warnings=0", "prettier --write"],
4 "*.html": ["prettier --write"],
5 "*.{scss,css}": ["prettier --write"],
6 "*.{json,md}": ["prettier --write"]
7}
8}Create Pre-Commit Hook
Edit .husky/pre-commit:
1npx lint-stagedUpdate package.json Scripts
1{
2 "scripts": {
3 "ng": "ng",
4 "start": "ng serve",
5 "build": "ng build",
6 "test": "ng test",
7 "lint": "ng lint",
8 "lint:fix": "ng lint --fix",
9 "format": "prettier --write \"src/**/*.{ts,html,scss,json}\"",
10 "format:check": "prettier --check \"src/**/*.{ts,html,scss,json}\"",
11 "prepare": "husky install"
12 }
13}Test the Setup
- Make a change to a file:
1echo "console.log('test')" >> src/app/app.component.ts - Stage the file:
1git add src/app/app.component.ts - Try to commit:
1git commit -m "test: verify hooks work"
- ESLint will check the file
- Prettier will format the file
- If errors exist, commit will be blocked
Useful Commands
Linting Commands
Check all files:1ng lint1ng lint project-name1ng lint --fix1ng lint --files="src/app/components/**/*.ts"Formatting Commands
Format all files:1npm run format1npm run format:check1prettier --write "src/app/components/**/*.{ts,html,scss}"Angular 19: New Features and Syntax
Signals - Modern Reactive State
Old approach (still works):1export class UserComponent {
2 userName = "John";
3
4 updateName(newName: string) {
5 this.userName = newName; // Manual change detection
6 }
7}1import { Component, signal, computed } from "@angular/core";
2
3export class UserComponent {
4 userName = signal("John");
5 userNameUpper = computed(() => this.userName().toUpperCase());
6
7 updateName(newName: string) {
8 this.userName.set(newName); // Automatic reactive updates
9 }
10}Modern Input/Output Functions
Old approach with decorators:1export class UserCardComponent {
2 @Input() userId!: string;
3 @Input() userName = "";
4 @Output() userSelected = new EventEmitter<string>();
5}1import { Component, input, output } from "@angular/core";
2
3export class UserCardComponent {
4 userId = input.required<string>(); // Required input
5 userName = input<string>(""); // Optional with default
6 userSelected = output<string>(); // Output event
7
8 selectUser() {
9 this.userSelected.emit(this.userId());
10 }
11}New Control Flow Syntax
Old syntax (*ngIf, *ngFor):1 <div *ngIf="user">
2 <h2>{{ user.name }}</h2>
3</div>
4
5<ul className="list-disc pl-6">
6 <li *ngFor="let item of items">{{ item }}</li>
7</ul>1 @if (user) {
2<div>
3 <h2>{{ user.name }}</h2>
4</div>
5} @for (item of items; track item.id) {
6<li>{{ item }}</li>
7} @switch (status) { @case ('active') {
8<span>Active</span>
9} @case ('inactive') {
10<span>Inactive</span>
11} @default {
12<span>Unknown</span>
13} }Generating Components in Angular 19
Generate standalone component (default):1ng generate component user-profile
2# or short form
3ng g c user-profile1import { Component } from "@angular/core";
2
3@Component({
4 selector: "app-user-profile",
5 standalone: true,
6 imports: [CommonModule],
7 templateUrl: "./user-profile.component.html",
8 styleUrl: "./user-profile.component.scss",
9})
10export class UserProfileComponent {}1ng g c user-card --inline-template --inline-styleMigration from Older Angular Versions
Step 1 — Update to Angular 19
1ng update @angular/core@19 @angular/cli@19Step 2 — Migrate to Standalone Components
1ng generate @angular/core:standalone- Converts components to standalone
- Updates imports and providers
- Removes NgModules
- Updates routing configuration
Step 3 — Adopt Signals (Optional but Recommended)
Manual migration of @Input() to input():
1// Before
2@Input() userName = '';
3
4// After
5userName = input<string>('');Manual migration of @Output() to output():
1// Before
2@Output() userClick = new EventEmitter<User>();
3
4// After
5userClick = output<User>();Recommended Complete Configuration for Angular 19 (Flat Config)
Here is the complete eslint.config.js with all recommended rules:
1// @ts-check
2
3module.exports = tseslint.config({
4 files: ["**/*.ts"],
5 extends: [
6 eslint.configs.recommended,
7 ...tseslint.configs.recommended,
8 ...tseslint.configs.stylistic,
9 ...angular.configs.tsRecommended,
10 prettier,
11 ],
12 processor: angular.processInlineTemplates,
13 rules: {
14 "@angular-eslint/directive-selector": [
15 "error",
16 { type: "attribute", prefix: "app", style: "camelCase" },
17 ],
18 "@angular-eslint/component-selector": [
19 "error",
20 { type: "element", prefix: "app", style: "kebab-case" },
21 ],
22 "@angular-eslint/no-output-on-prefix": "error",
23 "@angular-eslint/no-output-native": "error",
24 "@angular-eslint/no-input-rename": "error",
25 "@angular-eslint/use-lifecycle-interface": "error",
26 "@angular-eslint/prefer-on-push-component-change-detection": "warn",
27 "@typescript-eslint/no-explicit-any": "warn",
28 "@typescript-eslint/explicit-function-return-type": "off",
29 "no-console": "warn",
30 },
31},
32{
33 files: ["**/*.html"],
34 extends: [
35 ...angular.configs.templateRecommended,
36 ...angular.configs.templateAccessibility,
37 prettier,
38 ],
39 rules: {
40 "@angular-eslint/template/no-negated-async": "error",
41 "@angular-eslint/template/eqeqeq": "error",
42 "@angular-eslint/template/banana-in-box": "error",
43 "@angular-eslint/template/accessibility-alt-text": "warn",
44 "@angular-eslint/template/accessibility-label-for": "warn",
45 "@angular-eslint/template/accessibility-elements-content": "warn",
46 "@angular-eslint/template/click-events-have-key-events": "warn",
47 },
48});package.json (Complete Example)
Dependencies (Angular 19.2.x):1{
2 "name": "my-angular-app",
3 "version": "0.0.0",
4 "private": true,
5 "dependencies": {
6 "@angular/common": "^19.2.0",
7 "@angular/compiler": "^19.2.0",
8 "@angular/core": "^19.2.0",
9 "@angular/forms": "^19.2.0",
10 "@angular/platform-browser": "^19.2.0",
11 "@angular/platform-browser-dynamic": "^19.2.0",
12 "@angular/router": "^19.2.0",
13 "rxjs": "~7.8.0",
14 "tslib": "^2.3.0",
15 "zone.js": "~0.15.0"
16 }
17}1{
2 "devDependencies": {
3 "@angular-devkit/build-angular": "^19.2.17",
4 "@angular/cli": "^19.2.17",
5 "@angular/compiler-cli": "^19.2.0",
6 "@types/jasmine": "~5.1.0",
7 "angular-eslint": "20.5.1",
8 "eslint": "^9.38.0",
9 "eslint-config-prettier": "^9.1.0",
10 "husky": "^9.0.11",
11 "jasmine-core": "~5.6.0",
12 "karma": "~6.4.0",
13 "karma-chrome-launcher": "~3.2.0",
14 "karma-coverage": "~2.2.0",
15 "karma-jasmine": "~5.1.0",
16 "karma-jasmine-html-reporter": "~2.1.0",
17 "lint-staged": "^15.2.2",
18 "prettier": "^3.2.5",
19 "typescript": "~5.7.2",
20 "typescript-eslint": "8.46.2"
21 }
22}1{
2 "scripts": {
3 "ng": "ng",
4 "start": "ng serve",
5 "build": "ng build",
6 "watch": "ng build --watch --configuration development",
7 "test": "ng test",
8 "lint": "ng lint",
9 "lint:fix": "ng lint --fix",
10 "format": "prettier --write \"src/**/*.{ts,html,scss,json}\"",
11 "format:check": "prettier --check \"src/**/*.{ts,html,scss,json}\"",
12 "prepare": "husky install"
13 }
14}angular-eslintis a single unified package- Angular 19.2.x uses RxJS 7.8 and zone.js 0.15
- Testing setup includes Jasmine 5.1 and Karma 6.4
- TypeScript 5.7.2 is the current version for Angular 19
watchscript included by default for continuous builds
.prettierrc
1{
2 "semi": true,
3 "singleQuote": true,
4 "tabWidth": 2,
5 "useTabs": false,
6 "trailingComma": "es5",
7 "printWidth": 100,
8 "arrowParens": "always",
9 "endOfLine": "lf",
10 "bracketSpacing": true,
11 "bracketSameLine": false,
12 "htmlWhitespaceSensitivity": "css",
13 "overrides": [
14 {
15 "files": "*.html",
16 "options": {
17 "parser": "angular",
18 "printWidth": 120
19 }
20 }
21 ]
22}lint-staged Configuration (add to package.json)
1{
2 "lint-staged": {
3 "*.ts": ["eslint --fix --max-warnings=0", "prettier --write"],
4 "*.html": ["prettier --write"],
5 "*.{scss,css}": ["prettier --write"],
6 "*.{json,md}": ["prettier --write"]
7 }
8}.husky/pre-commit
1npx lint-staged.vscode/settings.json
1{
2 "editor.defaultFormatter": "esbenp.prettier-vscode",
3 "editor.formatOnSave": true,
4 "editor.codeActionsOnSave": {
5 "source.fixAll.eslint": "explicit"
6 },
7 "[typescript]": {
8 "editor.defaultFormatter": "esbenp.prettier-vscode"
9 },
10 "[html]": {
11 "editor.defaultFormatter": "esbenp.prettier-vscode"
12 },
13 "[scss]": {
14 "editor.defaultFormatter": "esbenp.prettier-vscode"
15 }
16}Resources
Angular 19
- Angular Official Docs
- Angular CLI
- Angular Style Guide
- Angular Signals Guide
- Standalone Components Guide
- Angular 19 Release Notes
New Angular 19 Features
ESLint for Angular
Prettier
Development Tools
Next Steps
Ready For Production Checklist
- Run typecheck:
1pnpm exec tsc --noEmit - Run linter and fix issues:
1pnpm exec eslint "app/**/*.{ts,tsx}" - Run formatter check:
1pnpm exec prettier --check "src/**/*.{ts,tsx,html,scss,json}" - Build the app:
1pnpm exec ng build --configuration production - Run unit and e2e smoke tests (Jasmine/Karma or Playwright)
- Verify CI passes and pre-commit hooks (Husky) are installed
- Read the common tool guides for deeper understanding:
- ESLint - General ESLint concepts
- Prettier - Advanced Prettier configuration
- Husky - More Git hooks examples
- lint-staged - Advanced configurations
- Learn about Angular 19 best practices:
- Standalone component architecture
- Signals-based state management (recommended over NgRx for simple cases)
- New
input()andoutput()functions - Reactive programming with RxJS (still important for async operations)
- Deferrable views with
@defer - Server-Side Rendering (SSR) with Angular Universal
- Set up additional tooling:
- Unit testing with Jasmine/Karma or Jest (modern alternative)
- E2E testing with Playwright (recommended) or Cypress
- CI/CD pipelines with GitHub Actions or Azure DevOps
- Explore related setups:
- EditorConfig - Editor consistency
- Backend integration (.NET, Node.js)