Activity 25: SOLID PRINCIPLES IN Angular

Understanding SOLID Principles and Their Application in Angular
In software development, SOLID principles are a set of design principles that make code more maintainable, scalable, and flexible. They guide developers in building robust systems by promoting modularity and reducing complexity. Angular, being a popular framework for building client-side applications, benefits greatly from these principles. In this article, we will explore the SOLID principles, how they apply to Angular, and include real-world examples and code snippets to clarify each concept.
S - Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have one, and only one, reason to change. This means that a class should only perform one task or responsibility.
Example in Angular
In Angular, services are used to handle business logic separately from components, promoting SRP.
// Bad Practice: Component handling multiple responsibilities
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
})
export class UserComponent {
user = { name: 'John', age: 22 };
// Handles both user data and logging responsibility
logUserInfo() {
console.log('User Info:', this.user);
}
}
Refactor using SRP:
// Good Practice: Separate responsibility into a service
@Injectable({
providedIn: 'root',
})
export class LoggerService {
log(message: string) {
console.log(message);
}
}
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
})
export class UserComponent {
user = { name: 'John', age: 22 };
constructor(private logger: LoggerService) {}
logUserInfo() {
this.logger.log(`User Info: ${this.user.name}, Age: ${this.user.age}`);
}
}
By applying SRP, you can separate the business logic into services, making the components lighter and easier to maintain.
O - Open/Closed Principle (OCP)
The Open/Closed Principle states that a class should be open for extension but closed for modification. This means that you should be able to extend a class’s behavior without modifying its existing code.
Example in Angular
In Angular, you can extend components or services through inheritance, or by using strategies like dependency injection.
// Bad Practice: Directly modifying an existing class
@Injectable({
providedIn: 'root',
})
export class PaymentService {
processPayment() {
console.log('Processing payment');
}
}
Refactor using OCP:
// Good Practice: Extend functionality without modifying existing class
@Injectable({
providedIn: 'root',
})
export class PaymentService {
processPayment() {
console.log('Processing payment');
}
}
@Injectable({
providedIn: 'root',
})
export class CreditCardPaymentService extends PaymentService {
processPayment() {
console.log('Processing credit card payment');
}
}
Instead of modifying the PaymentService each time, you can extend it, adhering to the OCP principle.
L - Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the functionality of a program.
Example in Angular
You can create services that can be replaced or substituted easily in different parts of the app.
// Base Service
@Injectable({
providedIn: 'root',
})
export class PaymentService {
processPayment() {
console.log('Processing payment');
}
}
// Substitutable Service
@Injectable({
providedIn: 'root',
})
export class PaypalPaymentService extends PaymentService {
processPayment() {
console.log('Processing PayPal payment');
}
}
When switching between different payment methods, you can substitute one payment service for another without changing the dependent components, preserving LSP.
I - Interface Segregation Principle (ISP)
The Interface Segregation Principle suggests that clients should not be forced to implement interfaces they do not use. Instead, multiple small, specific interfaces are preferred over a large, generalized one.
Example in Angular
You can define multiple small interfaces and implement only what is needed in the services or components.
// Bad Practice: Large interface with unnecessary methods
interface AuthService {
login(): void;
logout(): void;
register(): void;
resetPassword(): void;
}
// Good Practice: Split interface into smaller, focused ones
interface LoginService {
login(): void;
}
interface LogoutService {
logout(): void;
}
@Injectable({
providedIn: 'root',
})
export class UserService implements LoginService, LogoutService {
login() {
console.log('User logged in');
}
logout() {
console.log('User logged out');
}
}
By applying only the required interfaces, you can make your classes more flexible, adhering to ISP.
D - Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions.
Example in Angular
In Angular, services are injected into components, adhering to DIP by decoupling the high-level logic from low-level implementations.
// Bad Practice: Direct dependency on a low-level service
@Component({
selector: 'app-payment',
templateUrl: './payment.component.html',
})
export class PaymentComponent {
paymentService = new PaypalPaymentService();
processPayment() {
this.paymentService.processPayment();
}
}
Refactor using DIP:
// Good Practice: Depend on abstraction (interface or token)
@Component({
selector: 'app-payment',
templateUrl: './payment.component.html',
})
export class PaymentComponent {
constructor(private paymentService: PaymentService) {}
processPayment() {
this.paymentService.processPayment();
}
}
By injecting services through dependency injection, you can easily switch between different implementations without modifying the components.
Github Link:
https://github.com/RodelDecio/Solid-Principles-in-Angular.git



