4 min read - A Short Guide to TypeScript Component Naming: Angular and NestJS Best Practices
Frontend & Backend Development
Good naming conventions are like a well-designed API—they make your code self-documenting and reduce cognitive load for every developer who touches your codebase. In TypeScript applications, especially in enterprise frameworks like Angular and NestJS, consistent naming becomes even more critical as your application scales.
After working on dozens of Angular and NestJS projects, I've seen how poor naming decisions compound over time, creating confusion and slowing down development. Conversely, teams that establish clear naming conventions from day one move faster, onboard new developers more easily, and maintain higher code quality.
The Foundation: TypeScript Naming Conventions
TypeScript's type system gives us powerful tools for creating self-documenting code, but only if we use them consistently. Here are the core principles that apply across both Angular and NestJS:
Classes and Interfaces
Classes should use PascalCase and describe what they represent:
// ✅ Good
class UserService {}
class PaymentController {}
class DatabaseConnectionManager {}
// ❌ Avoid
class userservice {}
class paymentCtrl {}
class DBConnMgr {}
Interfaces should also use PascalCase, with a descriptive name that indicates the contract:
// ✅ Good
interface User {
id: string
email: string
}
interface PaymentGateway {
processPayment(amount: number): Promise<PaymentResult>
}
// ❌ Avoid - Hungarian notation is outdated
interface IUser {}
interface IPaymentGateway {}
Methods and Properties
Use camelCase for methods and properties, with names that clearly indicate their purpose:
// ✅ Good
class UserService {
async findUserById(id: string): Promise<User> {}
async updateUserProfile(userId: string, data: UpdateUserDto): Promise<User> {}
private validateEmailFormat(email: string): boolean {}
}
// ❌ Avoid
class UserService {
async getUser(id: string) {} // Too generic
async upd(id: string, d: any) {} // Abbreviated and unclear
}
Angular Component Naming Patterns
Angular's architecture provides specific guidance for naming different types of components and services. Following these conventions makes your application more maintainable and helps other Angular developers understand your code structure immediately.
Component Classes and Files
Angular components should use PascalCase for the class name and kebab-case for file names:
// file: user-profile.component.ts
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
})
export class UserProfileComponent {
// Component logic
}
// file: payment-history-card.component.ts
@Component({
selector: 'app-payment-history-card',
templateUrl: './payment-history-card.component.html',
})
export class PaymentHistoryCardComponent {
// Component logic
}
Services and Providers
Services should have descriptive names that indicate their responsibility:
// ✅ Good - Clear responsibility
@Injectable()
export class UserAuthenticationService {
login(credentials: LoginCredentials): Observable<AuthResult> {}
logout(): void {}
refreshToken(): Observable<string> {}
}
@Injectable()
export class PaymentProcessingService {
processPayment(request: PaymentRequest): Observable<PaymentResult> {}
validateCard(cardNumber: string): boolean {}
}
// ❌ Avoid - Too generic
@Injectable()
export class DataService {}
@Injectable()
export class UtilService {}
Directives and Pipes
Custom directives and pipes should have clear, action-oriented names:
// Directive - describes what it does
@Directive({
selector: '[appHighlightOnHover]',
})
export class HighlightOnHoverDirective {}
// Pipe - describes the transformation
@Pipe({ name: 'formatCurrency' })
export class FormatCurrencyPipe {
transform(value: number, currency: string): string {}
}
NestJS Module and Service Naming
NestJS follows similar conventions but has its own architectural patterns that influence naming decisions.
Controllers
Controllers should be named after the resource they manage, with clear HTTP method mappings:
// ✅ Good
@Controller('users')
export class UsersController {
@Get(':id')
async findUserById(@Param('id') id: string): Promise<UserResponseDto> {}
@Post()
async createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {}
@Put(':id/profile')
async updateUserProfile(
@Param('id') id: string,
@Body() updateProfileDto: UpdateProfileDto
): Promise<UserResponseDto> {}
}
// ❌ Avoid
@Controller('api/v1/user-management-endpoints')
export class UserMgmtCtrl {}
Services and Repositories
Services should have names that clearly indicate their business domain:
// ✅ Good - Domain-specific services
@Injectable()
export class UserAccountService {
async createAccount(userData: CreateAccountDto): Promise<User> {}
async deactivateAccount(userId: string): Promise<void> {}
}
@Injectable()
export class PaymentBillingService {
async calculateMonthlyBill(userId: string): Promise<BillingAmount> {}
async processRecurringPayment(subscriptionId: string): Promise<PaymentResult> {}
}
// Repository pattern
@Injectable()
export class UserRepository {
async findById(id: string): Promise<User | null> {}
async save(user: User): Promise<User> {}
async deleteById(id: string): Promise<void> {}
}
DTOs and Entity Classes
Data Transfer Objects and Entity classes should clearly indicate their purpose:
// ✅ Good - Clear purpose and direction
export class CreateUserDto {
email: string
password: string
firstName: string
lastName: string
}
export class UserResponseDto {
id: string
email: string
firstName: string
lastName: string
createdAt: Date
// Note: password is never included in response DTOs
}
export class UpdateUserProfileDto {
firstName?: string
lastName?: string
phoneNumber?: string
}
// Entity
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column({ unique: true })
email: string
@Column()
hashedPassword: string
}
Advanced Naming Strategies
Generic Type Parameters
Use meaningful names for generic type parameters, especially in complex scenarios:
// ✅ Good - Descriptive generic names
interface Repository<TEntity, TKey> {
findById(id: TKey): Promise<TEntity | null>
save(entity: TEntity): Promise<TEntity>
}
interface ApiResponse<TData, TError = ApiError> {
data?: TData
error?: TError
timestamp: Date
}
// ✅ Single letter for simple cases
interface List<T> {
items: T[]
length: number
}
Event and Callback Naming
Use consistent patterns for events and callbacks:
// Angular - Event emitters
@Component({})
export class UserProfileComponent {
@Output() userUpdated = new EventEmitter<User>()
@Output() profileDeleted = new EventEmitter<string>() // userId
onSaveProfile(): void {
// Handle save logic
this.userUpdated.emit(this.user)
}
}
// NestJS - Event handlers
@Injectable()
export class UserEventHandler {
@OnEvent('user.registered')
handleUserRegistered(payload: UserRegisteredEvent): void {}
@OnEvent('payment.processed')
handlePaymentProcessed(payload: PaymentProcessedEvent): void {}
}
Team Conventions and Tooling
Establishing Team Standards
Document your naming conventions in a style guide that covers:
- File and folder naming patterns
- Class, interface, and method naming rules
- Specific patterns for your domain (e.g., financial services, e-commerce)
- Abbreviation guidelines and approved acronyms
Automated Enforcement
Use ESLint rules to enforce naming conventions:
// .eslintrc.json
{
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "class",
"format": ["PascalCase"]
},
{
"selector": "interface",
"format": ["PascalCase"]
},
{
"selector": "method",
"format": ["camelCase"]
},
{
"selector": "property",
"format": ["camelCase"]
}
]
}
}
Code Review Checklist
Include naming convention checks in your code review process:
- Are class and interface names descriptive and follow PascalCase?
- Do method names clearly indicate their action and follow camelCase?
- Are DTOs properly named with their direction (Create, Update, Response)?
- Do file names match the class names and follow framework conventions?
The Long-Term Benefits
Consistent naming conventions pay dividends over time:
Faster Onboarding: New team members can understand code structure immediately
Reduced Bugs: Clear names reduce misunderstandings about component responsibilities
Better Tooling: IDEs can provide better autocomplete and refactoring support
Easier Maintenance: Future developers (including yourself) can navigate the codebase efficiently
At Exceev, we've seen how proper naming conventions can dramatically improve development velocity and code quality in large TypeScript applications. The investment in establishing these patterns early in a project pays for itself many times over as the application grows.
Remember: code is read far more often than it's written. Choose names that will make sense to your future self and your teammates six months from now.