Angular - Design Patterns



This chapter will discuss Design Patterns in Angular. It includes an introduction, its types, and advantages. After reading the entire chapter, you will have a clear understanding of Design Patterns.

Design Patterns

In software engineering, design patterns are like a reusable blueprint or a template for solving common problems in software design. A few of them might also known as a "software design pattern".

Design patterns help developers to apply best practices in software development, which enhance the performance, maintainability, and scalability of software applications. By using the specified design patterns, developers can solve common design problems more easily, which can lead to higher-quality software.

Let's take a simple scenario in an Angular application to understand it better.

Scenario: Singleton Pattern in an Angular Application

Suppose you are developing an Angular application that needs to create a service. You want to ensure that only one instance of the service class is created and used throughout the application. This is where the Singleton pattern comes into action.

Design Patterns in Angular

In Angular, design patterns are known methods (or techniques) for solving common design problems. Applications built using design patterns are more scalable and reliable. Additionally, design patterns also improve the application's performance by decreasing redundancy and loading components and modules when they are required.

We will discuss some important design patterns in the further chapters in detail, including their usage, syntax to create, advantages, implementation examples, etc.

Types of Design Patterns in Angular

Below is a list of commonly used Design Patterns in Angular application:

Let's discuss them briefly with a simple code snippet one by one.

Dependency Injection (DI)

In Angular, Dependency Injection (in short, DI) is a "design pattern" in which a class receives its dependencies from an external source instead of creating them itself.

This approach helps applications achieve loose coupling and reduce tight coupling between different parts of the application. By injecting dependencies, applications become more flexible and adaptable to changes.

Example

In the following example, you can see that the CalculatorComponent injects the MyCalcService dependencies rather than creating them itself. This service already exists in the application, so we use the Dependency Injection design pattern, which allows us to use this pre-existing dependency.

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MyCalcService } from '../my-calc.service';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-calculator',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './calculator.component.html',
  styleUrl: './calculator.component.css'
})
export class CalculatorComponent implements OnInit{
  //injecting service
  constructor(private myservice: MyCalcService){}
  n1: number = 10;
  n2: number = 20;
  add: number = 0;
  subtract: number = 0;
  multiply: number = 0;
  divide: number = 0;
  ngOnInit(): void(){
     this.add = this.myservice.add(this.n1, this.n2);
     this.subtract = this.myservice.subtract(this.n1, this.n2);
     this.multiply = this.myservice.multiply(this.n1, this.n2);
     this.divide = this.myservice.divide(this.n1, this.n2);
  }
}

Click the link to read in more detail

Lazy Loading

In Angular, the Lazy-loading is a "design pattern" developed by the google angular team to "enhance the application performs". The "lazy-loading" technique loads only the required component and module at a time rather than loading all together.

For example, suppose you have an Angular application with multiple feature modules, like a "dashboard", "user profile", "settings", and "reports". Instead of loading all these modules when the application starts, you can configure lazy loading to load these modules only when the user navigates to them.

Example

In this example, we will define routes for the modules, "Auth" and "Dashboard". By using the loadChildren property, we will set them as lazy-loaded modules, so they will only load when the relevant route is active in the URL.

For example, if the URL localhost:4200/auth is active, only the "Auth" module will be loaded, and same for localhost:4200/dashboard URL.

import { Routes } from '@angular/router';

export const routes: Routes = [
    {path: 'auth', 
    loadChildren:() => import('./auth/auth.module').then(m => m.AuthModule)},
    {path: 'dashboard', 
     loadChildren:() => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
    }
];

Click the link to read in more detail

Singleton Pattern

In Angular, the Singleton Pattern is a "design pattern" having a single instance of a class throughout the entire application and provide the global point of access to it.

This design pattern is useful when you want to share a common (or single) resource to the entire application without recreating it; such as Angular services.

Example

In the following example, we create a service class (i.e., a class defined by the @Injectable decorator) named MyCalcService, which has root-level access throughout the application.

The "MyCalcService" service class has a single instance within the entire application, which uses the singleton design pattern to share common logic throughout the entire application at the root level.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyCalcService {

  constructor() { }

  add(n1: number, n2: number){
    return n1 + n2;
  }

  subtract(n1: number, n2: number){
    return n1 - n2;
  }

  multiply(n1: number, n2: number){
    return n1 * n2;
  }

  divide(n1: number, n2: number){
    return n1 / n2;
  }

}

Click the link to read in more detail

Observer Pattern

In Angular, the Observer Pattern is a "design pattern" that allows an object to called the observable from RxJS library to "send notifications" to multiple observer objects that are interested in the state changes of the observable. This design pattern is useful in managing asynchronous data streams in Angular applications.

Example

In the example below, we will define a method, getRandColors() within the service class, which returns an observable.

It uses the Observer design pattern, which involves an "observable" (subject) and one or more "observers" (subscribers) that react to changes or updates when ever the method is called in the component.

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ObserverService {

  constructor() { }

  getRandColors(): Observable<string> { 
    return new Observable<string>(observer => { 
      let colors = ["red", "green", "black", "yellow", "blue", "pink", "gray"]; 
      let rand = Math.floor(Math.random() * colors.length); 
      observer.next(colors[rand]);
      observer.complete(); });
    }
}

Click the link to read in more detail

Advantages of Angular Design Patterns

Below is a list of some advantages of Angular Design Patterns

  • Scalability: Web applications developed using design patterns will be more scalable,
  • Reliability: Angular design patterns help to ensure that web applications are reliable, reducing the risk of errors and improving overall stability.
  • Maintainability: Angular design patterns promote code reusability and modularity, making it easier to maintain and update the application.

Conclusion

In conclusion, Design patterns in Angular are highly beneficial for developing and designing scalable applications. They help developers to solve complex design problems more easily and ensure the application remains maintainable and reliable.

Advertisements