SOLID Principles: Angular


S – Single Responsibility

Ex: A component cannot have multiple widgets which can instead be made as a reusable component for future.

 

Instead of

Do this

app.component.ts

<div>

<!--Navbar -->

  <nav>

  <ul>

    <li>Savings</li>

    <li>Deposits</li>

    <li>Loan</li>

  </ul>

  </nav>

  <!--card to display savings account details -->

  <div class="card-wrapper">

      <div class="header">CITI BANK</div>

    Account: AC29326262

    Balance: 5000

    Transactions avg: 3

     </div>

  </div>

  <!--card to display deposits details -->

  <div class="card-wrapper">

      <div class="header">CITI BANK</div>

    Account: AC29326262

    Deposits Total: 90000

    No. of Deposits: 6

     </div>

  </div>

</div>

app.component.ts

<nav-bar></nav-bar>

<card [type]=”savings”></card>

<card [type]=”deposits”></card>

 

navbar.component.ts

  <nav>

  <ul>

    <li>Savings</li>

    <li>Deposits</li>

    <li>Loan</li>

  </ul>

  </nav>

 

card.component.ts

<div *ngIf=”type == ‘savings’ >

<div class="card-wrapper">

      <div class="header">CITI BANK</div>

    Account: AC29326262

    Balance: 5000

    Transactions avg: 3

     </div>

  </div>

<div *ngIf=”type == ‘deposits >

<div class="card-wrapper">

      <div class="header">CITI BANK</div>

    Account: AC29326262

    Deposits Total: 90000

    No. of Deposits: 6

     </div>

  </div>

 

O- Open for extension, closed for modification

All the libraries are already good example of this as we only extend the use in out app but we do not have the control to the source file of the library. Above example is closed for modification, yes. Only savings and deposits type can be displayed. But not open for extension.

The savings and deposits cards should now be made as separate components.

Card component has to contain the header and <ng-content>

Savings and deposits components should use card component to fill the details in  <ng-content>

App.component.ts will have savings and deposits components

Now having just <card></card> will still display the structure of the card with empty content. It is now available for extension: <card> Test </card>

Card.component.ts

<div class="card-wrapper">

      <div class="header">CITI BANK</div>

      <ng-content></ng-content>

 </div>

App.component.html

Deposit.component.ts

Savings.component.ts

<savings></savings>

<deposits></deposits>

<card> test </card>

<card>

    Account: AC29326262

    Deposits Total: 90000

    No. of Deposits: 6

</card>

<card>

    Account: AC29326262

    Balance: 5000

    Transactions avg: 3

</card>

Output:


 

L – Liskov Substitution

Consider we have export to csv/download button in the content of both savings and deposits component. This is now redundant code. Let us have a separate download directive. This download directive now acts as a parent/base class


 

Parent/base component

Download.component.ts

@Directive({ selector: 'download' })

export class DownloadComponent {

  download() {

    alert('Downloading in parent class...');

  }

}

 

Now if savings component extends this base directive and also has the same ‘download()’ method, it is a violation of Liskov’s principle.

WRONG

RIGHT

Savings.component.ts

<button (click)=”download()”>

    Download

<button>

 

export class SavingsCardComponent extends DownloadComponent {

  download() {

    alert('I do not support it...');

  }

}

 

Output: ‘I do not support it’

Savings.component.ts

<button (click)=”download()”>

    Download

<button>

 

export class SavingsCardComponent extends DownloadComponent {

  download() {

    super.download();

    alert('I do not support it...');

  }

}

Output: ‘Downloading in parent class’

                ‘I do not support it’

 

Note: Implementation of the same function of the parent class should not be replaced.

 

I – Interface Segregation Service: Many client-specific interfaces are better than one general-purpose interface

One big interface is split into smaller interfaces as few properties might not be valid for all components that implement it

Ex: export interface ICard {

                Title:string,

                isLoading: Boolean,

                interest: number

}

Now, the deposits component needs to calculate interest but not savings component.

Savings.component.ts

Deposits.component.ts

export class savings implements ICard {

  Title=’’;

  isLoading=false;

  interest=0; //not needed. Still need to have atleast default as ICard is “implemented”

}

export class savings implements ICard {

  Title=’’;

  isLoading=false;

  interest=6;

}

 

So have another Interface:

export interface ICard {

                amount:number,

                isLoading: Boolean,

                interest: number

}

export interface ICard {

                amount:number,

}

export interface ICardInterest {

                isLoading: Boolean,

                interest: number

}

And import each of it separately where needed:

Savings.component.ts

Deposits.component.ts

export class savings implements ICard {

  amount=82323;

  isLoading=false;

  interest=0; //not needed. Still need to have atleast default as ICard is “implemented”

}

export class savings implements ICard, ICardInterest {

  amount=3737;

  isLoading=false;

  interest=6;

}

 

 

D - Dependency Inversion Principle: High level modules should not depend on low-level modules. It should depend upon abstraction

This has to be kept in mind every time Dependency Injection is used.

Consider you need to do some calculation in card component where <ng-content> is present based on whether the “interest” is present in that component or not. In card component you can use ‘@ContentChild()’ to load the deposits/savings component and check if “interest” property is present on not.

export class CardComponent {

  @Input() type;

  @ContentChild(DepositsCardComponent) depositCmp?: DepositsCardComponent;

  ngAfterContentInit() {

    alert(this.depositCmp);

    if (this.depositCmp) {

      alert('interest present....');

    }

  }

}

 

This is the violation of rule because there is a hard dependency on the component name directly. What we are interested in is any component in the ng-content that has the “interest” property.

So create abstraction:




StackBlitz Reference: https://angular-ivy-pguqxi.stackblitz.io



 

Comments

Popular posts from this blog

Inside the JavaScript Memory Box: Visualizing Variables, References, and Copies

React & State Management: All Concepts

React: Communication between components