How to Creating Reusable Angular Components

How to Creating Reusable Angular Components

In this article, I want to introduce you to the techniques available with Angular to build components by sharing as much code as possible:

  • Class inheritance
  • Class mixins
  • Component composition

Component Class Inheritance

My least favorite, but also the most used way to share code among Angular components, is ES6 class inheritance using the extends keyword.

ES6 class inheritance is seen as hugely controversial in the Javascript community for various reasons, but it is still incredibly used in the Angular world; when used in the right way and is not abused, the technique is a decent solution for sharing code between components.

Let’s see an example of extending a component with inheritance by creating a component ListComponent, extended by two more abstract classes that extend the functionality of the base class, and then we implement these with the actual Angular component.

A common way of using this technique is to create an abstract class and define there the methods shared by all the subclasses. A subclass may have to implement the abstract methods or override the existing ones.

ListComponent Base class

The Base class is very simple: we simply define the Input items.

export abstract class ListComponent {
  @Input() items: Item[];
}

Next, we want to extend the functionality of a simple list with pagination and selection. Therefore, we proceed and extend BaseList with two more abstract classes.

PageableListComponent

The component PageableListComponent extends ListComponent and adds pagination functionality.

export abstract class PageableListComponent extends ListComponent {
    page = 0;
    itemsPerPage = 2;
    get start() {
     return this.page * this.itemsPerPage;
    }
    get end() {
     return this.page * this.itemsPerPage + this.itemsPerPage;
    }
    get pages() {
      return new Array(this.items.length / this.itemsPerPage);
    }

    changePage(page: number) {
      this.page = page;
    }
}

SelectableListComponent

The component SelectableListComponent extends PageableListComponent and adds selection/unselection functionality.


export abstract class SelectableListComponent extends PageableListComponent {
  @Output() selected = new EventEmitter<Item>();
  @Output() unselected = new EventEmitter<Item>();

  selectedItems: Item[] = [];
  select(item: Item) {
    this.selected.emit(item);
    this.selectedItems = [...this.selectedItems, item];
  }

  unselect(item: Item) {
    this.unselected.emit(item);
    this.selectedItems = this.selectedItems.filter(({value}) => value !== item.value);
  }

  isItemSelected(item: Item) {
    return this.selectedItems.some(({value}) => item.value === value);
  }
}

Implementation component: CustomersListComponent

Finally, we create an implementation of the class CustomersListComponent and extend it SelectableListComponent. The template and the component will have access to all the outputs and inputs we specified in the other classes.

@Component({
  selector: 'customers-list',
  template: `
    <div *ngFor="let item of items | slice: start : end">
     <label>
       <input
         type="checkbox"
         [checked]="isItemSelected(item)"
         (change)="
           $event.target.checked ? select(item) : unselect(item)
         "
       />
      {{ item.display }}
     </label>
    </div>
    <div class='pages'>
    <div *ngFor="let p of pages; let i = index;" 
         class='page' 
         [class.selected]="i === page" 
         (click)="changePage(i)"
     >
     {{ i }}
    </div>
  </div>
`
})
export class CustomersListComponent extends SelectableListComponent {}
// USAGE
<customers-list [items]="customers" 
                (selected)="onSelected($event)"               
                (unselected)="onUnselected($event)"
></customers-list>

We can also create a subclass from CustomersListComponent, although the decorator’s metadata will have to be redefined. That means we will need to assign a new selector, template, styles, etc. to the new component. If you want to reuse them, then you can point the URLs to the parent class’:

@Component({
  selector: 'new-customers-list',
  templateUrl: '../customers-list/customers-list.component.html'
})
export class NewCustomersListComponent extends CustomersListComponent {}

Component Class Mixins

In order to share logic between Angular component classes, we can also leverage a less-known method known as Mixins. Mixins allow us to compose multiple small classes that extend the target class but without having to use multiple inheritance.

An Example of Typescript Mixin

Let’s demonstrate what a mixin is with a simple example. First, we define a base class:

class BaseButton {
  label: string;
  disabled: boolean;
}

Next, we define a function that extends the base class with a new mini-class

function themeMixin(BaseClass) {
    return class extends BaseClass {
      theme: string;
    }
}

Finally, we extend the BaseButton class with the mixin:


class PrimaryButton extends themeMixin(BaseButton) {}

Building CustomersListComponent using Mixins

Let’s rewrite the CustomersListComponent example using mixins.

export function pageableListMixin(BaseClass) {
  return class extends BaseClass {
    page = 0;
    itemsPerPage = 2;
    get pages() {
      return new Array(this.items.length / this.itemsPerPage);
    }

    changePage(page: number) {
      this.page = page;
    }
    get start() {
     return this.page * this.itemsPerPage;
    }
    get end() {
     return this.page * this.itemsPerPage + this.itemsPerPage;
    }
}
export function selectableListMixin(BaseClass) {
  class SelectableListMixin extends BaseClass {
    @Output() selected = new EventEmitter<Item>();
    @Output() unselected = new EventEmitter<Item>();

    selectedItems: Item[] = [];
    select(item: Item) {
      this.selected.emit(item);
      this.selectedItems = [...this.selectedItems, item];
    }

    unselect(item: Item) {
      this.unselected.emit(item);
      this.selectedItems = this.selectedItems.filter(({value}) => {
        return value !== item.value;
      });
    }

    isItemSelected(item: Item) {
      return this.selectedItems.some(({value}) => {
        return item.value === value;
      });
    }
  }

  return SelectableListMixin;
}

Once we define all the mixins we need to compose the component, we import the mixins and pass the Base class as an argument.

Then, we simply extend CustomersListComponent with the mixin CustomersListMixin.

const CustomersListMixin = 
  selectableListMixin(
    pageableListMixin(ListComponent)
  );
@Component(...)
export class CustomersListComponent extends CustomersListMixin {}

While also Mixins have several pitfalls, this is, in my opinion, a more elegant and safer solution to multiple inheritance, at least in the long term.

Component Composition

The component composition is a technique that complements inheritance and mixins: instead of extending a component with more functionality, we can combine multiple smaller components to achieve the same result.

ListComponent: Leveraging the Power of ngTemplateOutlet

The first component we can create is a generic, reusable component ListComponent: its responsibility is to simply render the items based on start and end indexes as provided by the parent component.

As you can see, the component does not dictate how to render each individual item: we let the parent define that by providing ngTemplateOutlet and passing each item as context.


@Component({
  selector: "list",
  template: `
    <div *ngFor="let item of items | slice : start : end">
      <ng-container 
       *ngTemplateOutlet="template; context: { item: item }"
      >
      </ng-container>
    </div>
`
})
export class ListComponent {
  @Input() items: Item[] = [];
  @Input() itemsPerPage = 2;
  @Input() currentPage: number;

  @ContentChild('item', { static: false }) 
  template: TemplateRef<any>;
  get start() {
    return this.currentPage * this.itemsPerPage;
  }
  get end() {
    return this.currentPage * this.itemsPerPage + this.itemsPerPage;
   }
}

PaginationComponent

Then, we add a pagination component that takes care of listing the page numbers, and to notify the parent when the user clicks on a page:

@Component({
  selector: "pagination",
  template: `
    <div class="pages">
      <div
       *ngFor="let p of pages; let i = index"
       class="page"
       [class.selected]="i === currentPage
       (click)="pageChanged.emit(i)"
      >{{ i }}
      </div>
   </div>
`
})
export class PaginationComponent {
  @Input() currentPage: number;
  @Input() itemsPerPage = 2;
  @Input() itemsLength: number;

  @Output() pageChanged = new EventEmitter<number>();

  get pages() {
    return new Array(this.itemsLength / this.itemsPerPage);
  }
}

CustomerComponent

Next, we define a component to represent each item in the list: it takes care of defining how the item is displayed, and dispatch events when the item is selected or unselected:

@Component({
  selector: "customer",
  template: ` 
    <label>
      <input
        type="checkbox"
        [checked]="isSelected"
        (change)="$event.target.checked ? selected.emit(item) : unselected.emit(item)"
      />
      {{ item.display }}
   </label>
`
})
export class CustomerComponent {
  @Input() item: Item;
  @Input() isSelected: boolean;
  @Output() selected = new EventEmitter<Item>();
  @Output() unselected = new EventEmitter<Item>();
}

CustomersListComponent

It’s now time to put things together! We can reuse the previously defined components to compose a list of customers, that is selectable and pageable. These components are all reusable and can be composed with any other list.


@Component({
  selector: "composition-customers-list",
  template: `
    <list
     [items]="items"
     [itemsPerPage]="2"
     [currentPage]="currentPage"
    >
     <ng-template #item let-item="item">
       <customer
        (selected)="selected($event)"
        (unselected)="unselected($event)"
        [item]="item"
        [isSelected]="isItemSelected(item)"
       ></customer>
     </ng-template>
    </list>

    <pagination
     [currentPage]="currentPage"
     [itemsLength]="items.length"
     [itemsPerPage]="2"
     (pageChanged)="currentPage = $event"
    ></pagination>
`
})
export class CompositionCustomersListComponent {
  @Input() items = [];

  currentPage = 0;
  selectedItems = [];

  selected(item) {
    this.selectedItems = [...this.selectedItems, item];
  }
  unselected(item) {
    this.selectedItems = this.selectedItems.filter(({ value }) => value !== item.value);
  }
  isItemSelected(item) {
    return this.selectedItems.some(({ value }) => item.value === value);
  }
}

Component composition is the ultimate way to create highly-reusable, clean, and effective components, and is easily my favorite way of thinking about sharing code and reusability.

Instead of writing God components, we can reuse many smaller ones. Getting right the public API of each component is fundamental for them to work well with the rest of your application.

As you can see above, we still have some repeated logic due to some methods being rewritten for each list we create: that’s why using one technique is not exclusive: we can easily combine this with a mixin that takes care of selection, so we do not have to rewrite it for other lists.

Source code

You can find all the examples’ code at this

In this article, we went through three techniques for sharing code between components.

If it wasn’t clear by now, I am not a fan of inheritance and multiple inheritances, but I think it’s still very important to know and recognize when it’s a good idea to use and when it’s not.

Happy Coding !

angular

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Install Angular - Angular Environment Setup Process

Install Angular in easy step by step process. Firstly Install Node.js & npm, then Install Angular CLI, Create workspace and Deploy your App.

Basics of Angular: Part-1

What is Angular? What it does? How we implement it in a project? So, here are some basics of angular to let you learn more about angular. Angular is a Typesc

Angular Sass: How To Use Sass In Angular 9 Tutorial

Sass in Angular is an extension of CSS that allows you to use things like variables, nested rules, inline imports. Angular supports Sass, CSS, and Less.

How to Upgrade Angular 8 To Angular 9

Angular is currently the most popular JavaScript framework and is used by several expert developers when developing single-page applications or powerful web apps. It has become quite crucial for the developers of Angular to add more features to the framework and fix the bugs . Let's learn how to upgrade Angular 8 to Angular 9 using CLI.

Visualizing data with NGX-Charts in Angular

Data Science, Data Analytics, Big Data, these are the buzz words of today's world. A huge amount of data is being generated and analyzed every day. So communica