Skip to main content

Angular Style Guide

Introduction

The Angular Style Guide represents a set of opinionated conventions and practices recommended by the Angular team and community. Following these guidelines helps you write clean, maintainable, and consistent code. This guide is particularly valuable when working in teams or on large projects where consistency becomes critical.

As a beginner, learning these conventions early will help you develop good habits that align with industry standards. In this guide, we'll explore the key recommendations from the official Angular Style Guide, adapted for those just starting their Angular journey.

Why Follow a Style Guide?

Before diving into specific conventions, let's understand why a style guide matters:

  • Consistency: Makes code predictable and easier to read
  • Maintainability: Helps future you and other developers understand the code
  • Best Practices: Incorporates learned wisdom from experienced developers
  • Productivity: Reduces decision fatigue by establishing conventions

File Structure and Naming Conventions

Single Responsibility Principle

One of the fundamental principles in Angular is that each file should have a single responsibility:

Do define one thing (component, service, pipe, etc.) per file.
Avoid defining multiple components, services, or other elements in the same file.

typescript
// Good: Single component in a file
// user-profile.component.ts
@Component({...})
export class UserProfileComponent {
// component logic here
}

// Avoid: Multiple components in one file
// users.ts - this contains multiple components
@Component({...})
export class UserProfileComponent {
// component logic here
}

@Component({...})
export class UserListComponent {
// component logic here
}

Naming Conventions

Naming is crucial for maintainability. Angular has established conventions for different artifacts:

File Names

Use consistent naming conventions with dots to separate the element type:

feature-name.type.ts

Examples:

  • user-profile.component.ts
  • authentication.service.ts
  • date-format.pipe.ts
  • user.model.ts

Class Names

Use upper camel case (PascalCase) for class names and append the type:

typescript
// Components
@Component({...})
export class UserProfileComponent { }

// Services
@Injectable()
export class AuthenticationService { }

// Pipes
@Pipe({...})
export class DateFormatPipe { }

// Directives
@Directive({...})
export class HighlightDirective { }

Selectors

Use kebab-case for component selectors (HTML tags):

typescript
// Component
@Component({
selector: 'app-user-profile',
// ...
})
export class UserProfileComponent { }

// Directive
@Directive({
selector: '[appHighlight]',
// ...
})
export class HighlightDirective { }

Component Structure

Component Organization

Organize your component class using this order:

  1. Properties (public before private)
  2. Constructor
  3. Lifecycle hooks
  4. Public methods
  5. Private methods
typescript
@Component({...})
export class UserProfileComponent implements OnInit {
// Properties
user: User;
isLoading = false;
private userId: string;

// Constructor
constructor(private userService: UserService) { }

// Lifecycle hooks
ngOnInit(): void {
this.loadUserProfile();
}

// Public methods
saveProfile(): void {
// implementation
}

// Private methods
private loadUserProfile(): void {
// implementation
}
}

Component Decorators

Organize metadata properties in the component decorator in a consistent way:

typescript
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss'],
providers: [UserProfileService],
changeDetection: ChangeDetectionStrategy.OnPush
})

Small, Focused Components

Do define small components with focused responsibilities.
Avoid large components that handle multiple concerns.

Large components are difficult to read, test, and maintain. Instead, break them down into smaller, more manageable components.

Services and Dependency Injection

Providing Services

For services used throughout the application:

typescript
// Preferred for application-wide services
@Injectable({
providedIn: 'root'
})
export class LoggerService { }

For feature-specific services:

typescript
// In a feature module
@NgModule({
providers: [UserService]
})
export class UserModule { }

Service Naming

  • Name services with their purpose in mind
  • Suffix service classes with Service
typescript
// Good
@Injectable({providedIn: 'root'})
export class AuthenticationService { }

// Avoid
@Injectable({providedIn: 'root'})
export class AuthenticationManager { }

Data Management

Interfaces for Models

Use interfaces to define data models:

typescript
// user.model.ts
export interface User {
id: string;
firstName: string;
lastName: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}

// Using the model
@Component({...})
export class UserProfileComponent {
user: User;

constructor(private userService: UserService) { }

ngOnInit(): void {
this.userService.getUser().subscribe((userData: User) => {
this.user = userData;
});
}
}

Use Strong Typing

Take advantage of TypeScript's typing system to make your code more robust:

typescript
// Avoid
getUserComments() {
return this.http.get('/api/comments');
}

// Better
getUserComments(): Observable<Comment[]> {
return this.http.get<Comment[]>('/api/comments');
}

Real-World Example: User Management Feature

Let's see how these style guidelines come together in a real-world example of a user management feature.

File Structure

/user-management
/components
/user-list
user-list.component.ts
user-list.component.html
user-list.component.scss
/user-detail
user-detail.component.ts
user-detail.component.html
user-detail.component.scss
/user-form
user-form.component.ts
user-form.component.html
user-form.component.scss
/services
user.service.ts
/models
user.model.ts
role.model.ts
/pipes
user-status.pipe.ts
user-management.module.ts
user-management-routing.module.ts

Components Example

typescript
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { User } from '../../models/user.model';
import { UserService } from '../../services/user.service';

@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit {
users$: Observable<User[]>;
isLoading = false;

constructor(private userService: UserService) { }

ngOnInit(): void {
this.loadUsers();
}

refreshUsers(): void {
this.loadUsers();
}

deleteUser(userId: string): void {
this.isLoading = true;
this.userService.deleteUser(userId).subscribe({
next: () => {
this.loadUsers();
},
error: (error) => {
console.error('Error deleting user:', error);
this.isLoading = false;
}
});
}

private loadUsers(): void {
this.isLoading = true;
this.users$ = this.userService.getUsers();
this.users$.subscribe({
complete: () => this.isLoading = false,
error: () => this.isLoading = false
});
}
}
html
<!-- user-list.component.html -->
<div class="user-list-container">
<h2>User Management</h2>

<div class="tools">
<button (click)="refreshUsers()">Refresh</button>
<a routerLink="new" class="btn-primary">Add User</a>
</div>

<div *ngIf="isLoading" class="loading">Loading users...</div>

<table *ngIf="(users$ | async) as users">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{ user.firstName }} {{ user.lastName }}</td>
<td>{{ user.email }}</td>
<td>{{ user.role }}</td>
<td>{{ user.status | userStatus }}</td>
<td>
<a [routerLink]="[user.id]">View</a>
<button (click)="deleteUser(user.id)">Delete</button>
</td>
</tr>
<tr *ngIf="users.length === 0">
<td colspan="5">No users found</td>
</tr>
</tbody>
</table>
</div>

Service Example

typescript
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

import { User } from '../models/user.model';

@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'api/users';

constructor(private http: HttpClient) { }

getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}

getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}

createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}

updateUser(id: string, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}

deleteUser(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}

Additional Style Guidelines

Use OnPush Change Detection

For better performance, consider using OnPush change detection:

typescript
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserCardComponent {
@Input() user: User;
}

Avoid Any Type

Avoid using the any type when possible:

typescript
// Avoid
function processData(data: any): any {
return data.map(item => item.name);
}

// Better
interface DataItem {
name: string;
[key: string]: any;
}

function processData(data: DataItem[]): string[] {
return data.map(item => item.name);
}

Consistent Import Ordering

Order your imports consistently:

  1. Angular imports
  2. Third-party imports
  3. Application imports
typescript
// Angular imports
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// Third-party imports
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';

// Application imports
import { User } from '../../models/user.model';
import { UserService } from '../../services/user.service';

Summary

Following Angular's Style Guide helps you write clean, maintainable, and consistent code. The key takeaways from this guide include:

  1. Single responsibility: One component/service/etc. per file
  2. Consistent naming: Use established naming conventions
  3. Small components: Keep components focused on a single responsibility
  4. Strong typing: Leverage TypeScript's type system
  5. Well-structured files: Maintain a consistent project structure
  6. Clear organization: Structure code within files consistently

By following these guidelines, you'll develop good habits that serve you well as you progress in your Angular journey, making collaboration easier and your code more maintainable.

Additional Resources

Exercise

Create a simple "Task Management" feature following the Angular Style Guide:

  1. Create a proper file structure
  2. Implement a Task interface
  3. Create a TaskService for CRUD operations
  4. Implement TaskListComponent and TaskDetailComponent
  5. Follow naming conventions and organizational principles

Verify that your implementation follows all the style guidelines discussed in this article.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)