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.
// 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:
// 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):
// 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:
- Properties (public before private)
- Constructor
- Lifecycle hooks
- Public methods
- Private methods
@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:
@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:
// Preferred for application-wide services
@Injectable({
providedIn: 'root'
})
export class LoggerService { }
For feature-specific services:
// In a feature module
@NgModule({
providers: [UserService]
})
export class UserModule { }
Service Naming
- Name services with their purpose in mind
- Suffix service classes with
Service
// 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:
// 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:
// 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
// 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
});
}
}
<!-- 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
// 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:
@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:
// 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:
- Angular imports
- Third-party imports
- Application imports
// 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:
- Single responsibility: One component/service/etc. per file
- Consistent naming: Use established naming conventions
- Small components: Keep components focused on a single responsibility
- Strong typing: Leverage TypeScript's type system
- Well-structured files: Maintain a consistent project structure
- 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
- Official Angular Style Guide
- Angular ESLint to automatically enforce some of these rules
- Angular Cheat Sheet for quick reference
Exercise
Create a simple "Task Management" feature following the Angular Style Guide:
- Create a proper file structure
- Implement a
Task
interface - Create a
TaskService
for CRUD operations - Implement
TaskListComponent
andTaskDetailComponent
- 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! :)