Understanding SOLID in Angular: How to Write Clean and Scalable Code

Dev Angular - Solid

If you’ve ever worked on an Angular project that started small but turned into a beast, you’ve probably felt the need for cleaner, more maintainable code. That’s where SOLID principles come in — a set of guidelines to help you write better software.

In this post, I’ll explain each letter of SOLID in simple terms and show how to apply these concepts in real Angular projects.

S – Single Responsibility Principle

What is it?
A class (or component, service, etc.) should have only one reason to change — meaning it should do just one job.

In Angular:
Avoid components that do everything: render UI, validate forms, call APIs, format dates, generate PDFs… you get the point.

Real-life example:

  • UserComponent → displays data.
  • UserService → fetches data from the backend.
  • DateFormatterService → formats dates.

Keeping responsibilities separate makes the code more testable and easier to maintain.

O – Open/Closed Principle

What is it?
Code should be open for extension but closed for modification. You should be able to add new behavior without changing existing code.

In Angular:
Use interfaces, dependency injection, or inheritance to extend features.

Real-life example:
Define an abstraction:

tsCopiarEditarexport interface Notifier {
  notify(message: string): void;
}

Create different implementations:

tsCopiarEditarexport class EmailNotifier implements Notifier {
  notify(message: string) { /* send email */ }
}

export class ToastNotifier implements Notifier {
  notify(message: string) { /* show toast */ }
}

Now you can extend notification behavior without touching the original logic.

L – Liskov Substitution Principle

What is it?
Subclasses should be substitutable for their base classes without breaking the behavior.

In Angular:
If you create a base component or service, the child class must honor the contract.

Real-life example:
Let’s say you have a BaseFormComponent with common form logic.
If you extend it with UserFormComponent, it should still behave correctly and not break existing expectations.

Avoid overriding methods in ways that change the meaning or expected behavior.

I – Interface Segregation Principle

What is it?
It’s better to have multiple small interfaces than one big, general one.

In Angular:
Instead of one bloated CrudService interface with create, read, update, delete, restore, archive, etc., split them into smaller, more specific interfaces.

Real-life example:

tsCopiarEditarexport interface Creatable<T> {
  create(item: T): Observable<T>;
}

export interface Readable<T> {
  getById(id: number): Observable<T>;
}

This way, services only implement what they actually use, keeping the code clean.

D – Dependency Inversion Principle

What is it?
Depend on abstractions, not concrete implementations.

In Angular:
Use interfaces and dependency injection to decouple your code.

Real-life example:

tsCopiarEditarexport abstract class LoggerService {
  abstract log(message: string): void;
}

Concrete implementation:

tsCopiarEditar@Injectable()
export class ConsoleLoggerService extends LoggerService {
  log(message: string) {
    console.log('[LOG]', message);
  }
}

Registering in AppModule:

tsCopiarEditarproviders: [
  { provide: LoggerService, useClass: ConsoleLoggerService }
]

Now, swapping the logger doesn’t require changing your app code — just the configuration.

Final Thoughts

Applying SOLID principles in Angular isn’t hard, and the benefits are massive:

  • Cleaner, more organized code
  • Easier to test and maintain
  • Less refactoring pain over time

Start small. Break down responsibilities, lean on interfaces, and always ask yourself: “Is this easy to understand and maintain?”

If you found this useful, share it with your dev team — especially the one who’s always building those 2,000-line components 😅

Post Comment