Skip to main content

Angular Style Guide

This guide covers a range of style conventions for Angular application code. These recommendations are not required for Angular to work, but instead establish a set of coding practices that promote consistency across the Angular ecosystem.

Naming Conventions

File Naming

Use Hyphens

Separate words within a file name with hyphens (-). For example, a component named UserProfile has a file name user-profile.ts.

Match Identifiers

File names should generally describe the contents of the code in the file. When the file contains a TypeScript class, the file name should reflect that class name.
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrl: './user-profile.component.css'
})
export class UserProfileComponent {
  // Component logic
}

Component Files Structure

Components typically consist of one TypeScript file, one template file, and one style file. These files should share the same name with different file extensions:
user-profile/
├── user-profile.component.ts
├── user-profile.component.html
├── user-profile.component.css
└── user-profile.component.spec.ts
For unit tests, end file names with .spec.ts. For example, the unit test file for the UserProfile component has the file name user-profile.spec.ts.

Project Structure

Source Directory Organization

All of your Angular UI code (TypeScript, HTML, and styles) should live inside a directory named src. Code that’s not related to UI, such as configuration files or scripts, should live outside the src directory.
my-angular-app/
├── src/
│   ├── app/
│   │   ├── features/
│   │   ├── shared/
│   │   └── main.ts
│   ├── assets/
│   └── index.html
├── angular.json
├── package.json
└── tsconfig.json

Feature-Based Organization

Organize your project into subdirectories based on the features of your application or common themes to the code in those directories:
src/app/
├── movie-reel/
│   ├── show-times/
│   │   ├── film-calendar/
│   │   └── film-details/
│   └── reserve-tickets/
│       ├── payment-info/
│       └── purchase-confirmation/
└── user/
    ├── profile/
    └── settings/
Avoid creating subdirectories based on the type of code that lives in those directories. For example, avoid creating directories like components, directives, and services.

Bootstrap in main.ts

The code to start up, or bootstrap, an Angular application should always live in a file named main.ts. This represents the primary entry point to the application.
main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

Dependency Injection

Prefer inject() Function

Prefer using the inject() function over injecting constructor parameters. The inject() function offers several style advantages:

Better Readability

More readable, especially when a class injects many dependencies

Better Type Inference

Offers better type inference and easier to add comments
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-user-dashboard',
  template: `<div>{{ userName() }}</div>`
})
export class UserDashboardComponent {
  // Cleaner and more readable
  private userService = inject(UserService);
  private logger = inject(LoggerService);
  
  userName = computed(() => this.userService.getCurrentUser().name);
}

Components and Directives

Component Selectors

Always use an application-specific prefix for component selectors to avoid naming conflicts with third-party components:
@Component({
  selector: 'app-user-profile',  // ✅ Good: uses 'app' prefix
  // ...
})
export class UserProfileComponent {}

@Component({
  selector: 'user-profile',  // ❌ Avoid: no prefix
  // ...
})
export class UserProfileComponent {}

Member Access Modifiers

Use protected on class members that are only used by a component’s template:
import { Component, computed, input } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  template: `<p>{{ fullName() }}</p>`
})
export class UserProfile {
  firstName = input<string>();
  lastName = input<string>();

  // `fullName` is not part of the component's public API, but is used in the template
  protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}

Use readonly for Angular Properties

Mark component and directive properties initialized by Angular as readonly. This includes properties initialized by input, model, output, and queries:
import { Component, input, output, model, viewChild } from '@angular/core';

@Component({
  selector: 'app-user-form',
  template: `...`
})
export class UserFormComponent {
  readonly userId = input<string>();
  readonly userSaved = output<void>();
  readonly userName = model<string>();
  readonly submitButton = viewChild<ElementRef>('submit');
}

Organize Class Members

Group Angular-specific properties together, typically near the top of the class declaration:
import { Component, inject, input, output, viewChild } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user-detail',
  template: `...`
})
export class UserDetailComponent {
  // 1. Injected dependencies
  private userService = inject(UserService);
  
  // 2. Inputs
  readonly userId = input.required<string>();
  readonly editable = input(false);
  
  // 3. Outputs
  readonly userUpdated = output<User>();
  
  // 4. Queries
  readonly nameInput = viewChild<ElementRef>('name');
  
  // 5. Other properties
  protected isLoading = signal(false);
  
  // 6. Methods
  saveUser() {
    // Implementation
  }
}

Template Best Practices

Prefer class and style Bindings

Prefer class and style bindings over using the NgClass and NgStyle directives:
<div 
  [class.admin]="isAdmin" 
  [class.dense]="density === 'high'"
  [style.color]="textColor"
  [style.background-color]="backgroundColor">
  Content
</div>

<!-- OR with object syntax -->
<div 
  [class]="{admin: isAdmin, dense: density === 'high'}"
  [style]="{'color': textColor, 'background-color': backgroundColor}">
  Content
</div>
Both class and style bindings use a more straightforward syntax that aligns closely with standard HTML attributes. Additionally, they incur less performance overhead compared to the NgClass and NgStyle directives.

Name Event Handlers Meaningfully

Prefer naming event handlers for the action they perform rather than for the triggering event:
<button (click)="saveUserData()">Save</button>
<button (click)="deleteUser()">Delete</button>
<textarea (keydown.control.enter)="commitNotes()"></textarea>

Keep Lifecycle Methods Simple

Avoid putting long or complex logic inside lifecycle hooks. Instead, create well-named methods to contain that logic:
export class AppComponent implements OnInit {
  ngOnInit() {
    this.startLogging();
    this.runBackgroundTask();
    this.loadUserPreferences();
  }
  
  private startLogging() {
    this.logger.setMode('info');
    this.logger.monitorErrors();
  }
  
  private runBackgroundTask() {
    // Complex logic here
  }
}

Avoid Complex Template Logic

When the code in a template gets too complex, refactor logic into the TypeScript code, typically with a computed signal:
@Component({
  selector: 'app-product-list',
  template: `
    @for (product of filteredProducts(); track product.id) {
      <div>{{ product.name }} - {{ displayPrice(product) }}</div>
    }
  `
})
export class ProductListComponent {
  products = signal<Product[]>([]);
  searchTerm = signal('');
  
  // Complex logic in TypeScript
  protected filteredProducts = computed(() => {
    const term = this.searchTerm().toLowerCase();
    return this.products().filter(p => 
      p.name.toLowerCase().includes(term) ||
      p.category.toLowerCase().includes(term)
    );
  });
  
  protected displayPrice(product: Product): string {
    return `$${product.price.toFixed(2)}`;
  }
}

TypeScript Best Practices

Use Lifecycle Hook Interfaces

When adding a lifecycle hook to your class, import and implement these interfaces to ensure that the methods are named correctly:
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-user-list',
  template: `...`
})
export class UserListComponent implements OnInit, OnDestroy {
  ngOnInit() {
    // The OnInit interface ensures this method is named correctly
  }
  
  ngOnDestroy() {
    // The OnDestroy interface ensures this method is named correctly
  }
}

Prefer const over let

Use const wherever possible, only using let when a value must change. Avoid var unless absolutely necessary:
// ✅ Good
const maxUsers = 100;
const apiUrl = 'https://api.example.com';
let currentPage = 1;  // Use let when value changes

// ❌ Avoid
var maxUsers = 100;  // Don't use var
let apiUrl = 'https://api.example.com';  // Use const for constants

Avoid any Type

Avoid any where possible. Consider whether a generic or unknown may be appropriate:
function processData<T>(data: T): T {
  return data;
}

function handleError(error: unknown) {
  if (error instanceof Error) {
    console.error(error.message);
  }
}

Summary

  • Use hyphen-case for file names (e.g., user-profile.component.ts)
  • Use application-specific prefixes for selectors (e.g., app-user-profile)
  • Use PascalCase for class names
  • Use camelCase for functions and methods
  • Use UPPER_SNAKE_CASE for constants
  • Organize by feature, not by type
  • Keep related files together in the same directory
  • Bootstrap in main.ts directly inside src
  • One concept per file
  • Use inject() function over constructor injection
  • Use protected for template-only members
  • Use readonly for Angular-initialized properties
  • Group Angular properties before methods
  • Keep lifecycle methods simple
  • Prefer class and style bindings over NgClass/NgStyle
  • Name event handlers for what they do
  • Avoid complex template logic
  • Refactor complex expressions to computed signals

Next Steps

Learn about performance optimization techniques in Angular

Build docs developers (and LLMs) love