DEV Community

Cover image for Angular — Abstract Design Pattern-DRY & Single Shared Responsibility
V.D
V.D

Posted on

Angular — Abstract Design Pattern-DRY & Single Shared Responsibility

Abstract Method pattern is a popular design pattern used in Angular applications. It is a behavioral design pattern that defines a skeletal structure of an algorithm in an operation, leaving the implementation details to be filled in by the sub-classes. In this blog, we will explore the Abstract Method pattern in Angular and how it can be implemented in our applications.

First, let’s understand what an Abstract Method pattern is?

Abstract Method pattern is a way to define a set of methods that must be implemented by the sub-classes. It is a template method pattern that defines the steps of an algorithm, but leaves the implementation of the steps to be defined by the sub-classes. The Abstract Method pattern is useful in situations where a set of objects have some common behavior, but they also have some different behavior that needs to be defined in the sub-classes.

The Abstract Method pattern is a design pattern in TypeScript that provides an abstract base class which contains one or more abstract methods. These abstract methods are then implemented by concrete subclasses, which provide the specific implementation details for the abstract methods. This pattern is useful when you want to define a set of behaviors that must be implemented by concrete subclasses, but you don’t want to specify the implementation details in the base class.


One of the key benefits of the Abstract Method pattern is that it allows you to define a set of behaviors that must be implemented by concrete subclasses, while still providing some common functionality that can be shared across all subclasses. This can make it easier to maintain your code over time, since you can update the common functionality in the abstract base class without having to modify each individual subclass.

Another benefit of the Abstract Method pattern is that it can help to enforce good design practices, since it requires that subclasses implement specific methods in a consistent way. This can help to ensure that your code is more predictable and less prone to errors or unexpected behavior.


When using the Abstract Method pattern, it’s important to keep in mind that abstract methods must be implemented by all concrete subclasses. If a subclass fails to implement an abstract method, you’ll get a compilation error, which can help catch bugs early in the development process.

In addition to defining abstract methods, the Abstract Method pattern can also be used to define abstract properties. Abstract properties are similar to abstract methods, but instead of defining a method signature, they define a property signature. This can be useful when you want to ensure that a property is implemented by all subclasses, but you don’t want to provide a default implementation in the abstract base class.

By defining an abstract base class that contains common functionality and one or more abstract methods, we can create a set of concrete subclasses that provide specific implementations for each method. This allows us to reuse common functionality across multiple components, while still allowing each component to be customized as needed.

Here’s an example of how the Abstract Method pattern can be used with multiple components in an Angular application:

Suppose we have a set of UI components that all need to display some common data, but each component needs to display that data in a slightly different way. For example, we might have a table component, a card component, and a list component, each of which needs to display a list of users in a different format.

To implement this functionality using the Abstract Method pattern
we might start by defining an abstract base class that contains the common functionality for fetching and displaying the data. Here’s an example of what that might look like:

import { Component, OnInit } from '@angular/core';

abstract class UserListComponent implements OnInit {
  users: any[];

  ngOnInit(): void {
    this.fetchUsers();
  }

  abstract fetchUsers(): void;
  abstract displayUsers(): void;
}
Enter fullscreen mode Exit fullscreen mode

In this example, we’ve defined an abstract class called UserListComponent that extends the Angular Component class and implements the OnInit interface. This class contains two abstract methods, fetchUsers and displayUsers, that we'll use to define the specific functionality for each of our UI components.

// Create a concrete subclass for a table component
@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent extends UserListComponent {
  fetchUsers(): void {
    // Fetch users data from API and store it in the 'users' property
  }

  displayUsers(): void {
    // Display users data in a table format
  }
}
-----------------------------------------------------------------------
// Create a concrete subclass for a card component
@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent extends UserListComponent {
  fetchUsers(): void {
    // Fetch users data from API and store it in the 'users' property
  }

  displayUsers(): void {
    // Display users data in a card format
  }
}
-----------------------------------------------------------------------
// Create a concrete subclass for a list component
@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css']
})
export class ListComponent extends UserListComponent {
  fetchUsers(): void {
    // Fetch users data from API and store it in the 'users' property
  }

  displayUsers(): void {
    // Display users data in a list format
  }
}
Enter fullscreen mode Exit fullscreen mode

We’ve then created three concrete subclasses of UserListComponent, one for each of our UI components: a table component, a card component, and a list component. Each of these subclasses extends UserListComponent and provides a specific implementation for the fetchUsers and displayUsers methods.

By using the Abstract Method pattern, we can create a set of reusable components that share common functionality while still allowing each component to provide its own unique behavior. This makes our code more maintainable, extensible, and easier to understand.

Let’s say we want to display all three views on the same page, side by side. We can achieve this by creating a parent component that simply includes the three child components:

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent {
  constructor() { }
}
<div class="user-list-container">
  <app-table></app-table>
  <app-list></app-list>
  <app-tile></app-tile>
</div>
Enter fullscreen mode Exit fullscreen mode

This approach allows us to keep the code for each view separate and organized, while still allowing us to display them all together in a single page or view.

Let’s say we want to add a search feature to our user list component. We can define a new abstract method searchUsers in our UserListComponent:

export abstract class UserListComponent implements OnInit {
  users: User[] = [];

  constructor() { }

  ngOnInit() {
    this.fetchUsers();
  }

  abstract fetchUsers(): void;

  abstract displayUsers(): void;

  abstract searchUsers(term: string): void;
}
Enter fullscreen mode Exit fullscreen mode

Now each of our child components can implement this method in their own way. Here’s an example implementation for the TableComponent:

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent extends UserListComponent {
  searchUsers(term: string): void {
    // Filter users data based on search term
    this.users = this.users.filter(user => user.name.toLowerCase().includes(term.toLowerCase()));
    this.displayUsers();
  }

  fetchUsers(): void {
    // Fetch users data from API and store it in the 'users' property
  }

  displayUsers(): void {
    // Display users data in a table format
  }
}

<div class="user-list-container">
  <app-table></app-table>
  <app-list></app-list>
  <app-tile></app-tile>
</div>
<app-search (search)="onSearch($event)"></app-search>
Enter fullscreen mode Exit fullscreen mode

Here, we’ve added a search bar component (app-search) to our page and defined an event handler onSearch that calls the searchUsers method on the currently displayed child component. The search event emitted by the search bar component contains the search term entered by the user.

Conclusion

In summary, the Abstract Method pattern can be a powerful tool for creating modular and reusable code in an Angular application, especially when working with multiple components that need to share a common interface or behavior. By defining an abstract base class with abstract methods and having multiple components extend and implement those methods, we can achieve a high degree of flexibility and maintainability in our code.

Overall, the Abstract Method pattern can be a useful tool for designing object-oriented systems in TypeScript. By defining abstract methods in an abstract base class, you can ensure that your code is more flexible, extensible, and maintainable over time.

Top comments (0)