Activity 25: SOLID PRINCIPLES in Angular

SMILING EVERYDAY FOR GOODIE.
SOLID Principles in Angular:
SOLID principles are a set of design principles aimed at making software more maintainable, flexible, and reusable. They promote modularity, decoupling, and a clear separation of concerns, which are crucial for building robust and scalable applications, especially in frameworks like Angular.
1. Single Responsibility Principle (SRP):
Explanation:
SRP encourages breaking down code into smaller, focused units, each responsible for a specific task. This approach simplifies maintenance, as changes to one responsibility won't affect others.
// Violating SRP
class UserService {
getUserData() {
// ...
}
saveUserData() {
// ...
}
sendNotifications() {
// ...
}
}
// Following SRP
class UserDataService {
getUserData() {
// ...
}
saveUserData() {
// ...
}
}
class NotificationService {
sendNotifications() {
// ...
}
}
Real-world Use Case: In an Angular application, you might have a component responsible for displaying a product list. Instead of having the component handle fetching data, displaying it, and applying filters, you can separate these tasks into different services: ProductService for data fetching, ProductDisplayService for rendering, and ProductFilterService for filtering.
2. Open/Closed Principle (OCP):
Explanation:
OCP promotes extensibility without altering existing code. This is achieved through techniques like interfaces and inheritance, allowing new functionalities to be added without breaking existing code.
// Violating OCP
class Product {
calculatePrice() {
// ...
}
}
// Following OCP
interface Product {
calculatePrice(): number;
}
class RegularProduct implements Product {
calculatePrice() {
// ...
}
}
class DiscountedProduct implements Product {
calculatePrice() {
// ...
}
}
Real-world Use Case: Imagine you have a component displaying products. You want to introduce different types of products, like discounted products or subscription-based products. Implementing the OCP allows you to create new product types without modifying the existing Product class, ensuring your code remains stable.
3. Liskov Substitution Principle (LSP):
Explanation:
LSP ensures that subclasses behave as expected when used interchangeably with their parent classes. This principle prevents errors by ensuring that subtypes maintain the same behavior and contract as their base types.
// Violating LSP
class Shape {
area(): number {
return 0;
}
}
class Rectangle extends Shape {
width: number;
height: number;
area(): number {
return this.width * this.height;
}
}
class Square extends Shape {
side: number;
area(): number {
return this.side * this.side;
}
}
const square = new Square();
square.side = 5;
const rectangle = new Rectangle();
rectangle.width = 5;
rectangle.height = 3;
// The following code will throw an error because Square doesn't meet the contract of Rectangle
rectangle.width = square.side; // This line violates LSP
rectangle.height = square.side;
Real-world Use Case: In an Angular component, you might have a base Product class and two subclasses: RegularProduct and DiscountedProduct. LSP ensures that you can use these subclasses interchangeably without unexpected behavior. For example, a ProductListComponent should be able to display both RegularProduct and DiscountedProduct without needing separate logic.
4. Interface Segregation Principle (ISP):
Explanation:
ISP emphasizes breaking down large interfaces into smaller, more specific ones. This makes it easier for clients to use only the methods they need, reducing unnecessary dependencies and promoting code flexibility.
// Violating ISP
interface IEmployee {
work(): void;
manageTeam(): void;
reportToManager(): void;
}
class Developer implements IEmployee {
work() {
// ...
}
manageTeam() {
// ...
}
reportToManager() {
// ...
}
}
class Manager implements IEmployee {
work() {
// ...
}
manageTeam() {
// ...
}
reportToManager() {
// ...
}
}
// Following ISP
interface IWorker {
work(): void;
}
interface IManager {
manageTeam(): void;
reportToManager(): void;
}
class Developer implements IWorker {
work() {
// ...
}
}
class Manager implements IManager {
manageTeam() {
// ...
}
reportToManager() {
// ...
}
}
Real-world Use Case: In an Angular application, you might have components that interact with different services. By applying ISP, you can create specific interfaces for each service, allowing components to depend only on the methods they need. For example, a ProductDetailComponent might only need to interact with the ProductService to fetch product data, while a CartComponent might need to interact with both the ProductService and CartService.
5. Dependency Inversion Principle (DIP):
Explanation:
DIP promotes loose coupling by introducing abstractions (interfaces) between modules. This allows high-level modules to interact with low-level modules through these abstractions, reducing dependencies and enhancing flexibility.
// Violating DIP
class OrderService {
private paymentProcessor = new StripePaymentProcessor();
processOrder() {
// ...
this.paymentProcessor.processPayment();
// ...
}
}
class StripePaymentProcessor {
processPayment() {
// ...
}
}
// Following DIP
interface IPaymentProcessor {
processPayment(): void;
}
class OrderService {
private paymentProcessor: IPaymentProcessor;
constructor(paymentProcessor: IPaymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
processOrder() {
// ...
this.paymentProcessor.processPayment();
// ...
}
}
class StripePaymentProcessor implements IPaymentProcessor {
processPayment() {
// ...
}
}
class PayPalPaymentProcessor implements IPaymentProcessor {
processPayment() {
// ...
}
}
Real-world Use Case: In an Angular application, you might have a UserService that depends on a DatabaseService to fetch user data. By applying DIP, you can introduce an interface IDataService and have both UserService and DatabaseService implement it. This allows you to switch to a different data source (e.g., a cloud database) without modifying the UserService.




