Building Reusable Components in Angular.

Building Reusable Components in Angular.

Why Components and Why Make Them Reusable?

According to Angular.io, Angular is a platform and framework for building single-page client applications using HTML and TypeScript. Basically, it is a front-end framework used to build single-page applications.

A single-page website or web application would mean a website or web application that renders just a page or dynamically rewrites a page or parts of it with new data from the server without loading a new page, as opposed to a website or web application that renders multiple new pages for different data.

Components, in Angular, are the building blocks of the UI. A component in Angular will have an HTML template for the view, a CSS style file, a typescript class that defines the behaviour of the view, and a testing specification file.

Now, you ask, why make the component reusable?

Imagine building a single-page application, like an admin dashboard and on most of the sections referenced on the navigation bar, you have a card that shows the statistics for the section or a data table. It will be redundant to create a new card or table for every section that there is, not to talk of making your code unnecessarily bulky. In a situation like that, it will be more efficient to create a card component or a table component that can be reused in the different sections.

Reusable Components

I will break down the process of building a reusable component into two.

  1. Building a reusable component using @input() and @output() decorators

  2. Building a reusable component using ng-content directive

Let's get started...

Building a reusable component using @input() and @output() decorator

For this section, let's say we are building a reusable table component. For starters, we know that mostly the table HTML structure will be constant. What will change each time is the data rendered in the table.

In light of this, we begin by creating a component with a regular table template.


<div class="card default-card users-table">
    <table class="normal-view d-none d-xl-table">
        <thead>
            <tr>
                <th *ngFor="let header of tableColumn" class="table">
                    <div class="th-with-filter">
                        <p>{{header?.title}}</p>
                    </div>
                </th>
            </tr>
        </thead>
        <tbody *ngIf="tableContent.length>0">
            <tr *ngFor="let content of tableContent">
                <td *ngFor="let header of tableColumn" class="table">
                    <div class="col" (click)="onRowClicked(rowAction, content)">
                        {{ content[header.dataField] }}
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { TableColumnModel, TableActionModel } from 'src/app/model/table.model';

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit {

  // table inputs and outputs required ****
  @Input() tableColumn: TableColumnModel[] 
  @Input() tableContent
  @Input() rowAction;
  @Input() tableActions: TableActionModel[];
  @Output() eventAction = new EventEmitter();

  constructor() { }

  ngOnInit() {
  }

  onRowClicked(event, data){
    this.eventAction.emit({ event, data});
  }

}

To better understand the code above, let me explain the basic elements/concepts involved.

  • @Input(): this is a decorator used in angular before a variable to show that the variable is an input to the component. The variable will get its value from the subscribing component or the component that will use the reusable component. In essence, it is inputting data to the reusable component. To use this decorator, you have to import "Input" from "@angular/core".

  • @Output(): this is also a decorator, but this decorator is used before a method that will emit an event from the reusable component to the subscribing component or the component using the reusable component. So in essence, it is outputting/emitting an event to the subscribing component. To use the @output() decorator to emit an event, you have to import "Output" as well as "EventEmitter" from "@angular/core". After decorating the desired variable with the @Output() decorator, we now set it as a new EventEmitter type to allow it emit events.

In the code above, in the 'table.component.html', there is a basic table with a header row that uses *ngFor directive to iterate through the tableColumn. Looking at the 'table.component.ts' file, the tableColumn is an input variable that will get its value from the subscribing component. The data that will be passed to the tableColumn in the subscribing component will look something like this

this.tableColumn = [ 
    { title:'Start Date', dataField:'Start_Date', class:'start-date' }, 
    { title:'Interest', dataField:'Interest', class:'interest' }, 
    { title:'Principal', dataField:'Principal', class:'principal'}, 
    { title:'Total Due', dataField:'Total_Due', class:'total-due'}
  ]

The same concept applies to the tableContent, for the content of the table. The data will be passed to the tableContent in the subscribing component and will look something like this

this.data= res.data.map((d)=>{
            return {
              id: d.id,
              user_id: d.user_id,
              Start_Date: d.start_date? moment(d.start_date).format('MMM Do YYYY') : '',
              Interest: d.interest,
              Principal: d.principal,
              Total_Due: d.total_due
            }
          });

The onRowClicked() method as can be seen in both the .ts file and the .html file is a method that emits an event when the HTML div is clicked. In addition to emitting an event, it also passes along data that can be used by the subscribing component. It should be noted that the table component is the one emitting an event and passing along data. So the data being passed along must be available in the table component. Although in most cases, it is an input previously gotten from the subscribing component.

This event emitted and data passed along can then be used by the subscribing component to perform any event it desires.

In the subscribing component, the selector of the table component is used as a custom HTML tag and the inputs and outputs to the table component are binded to attributes and properties of this custom tag. For example

<div class="table">
    <div class="row">
        <div class="col-xl-12">
            <app-table 
                [tableColumn]="tableColumn" 
                [tableContent]="data" 
                (eventAction)="onRowActionRecieved($event)"
                [rowAction]="rowAction"
            >
            </app-table>
        </div>
    </div>
</div>

The onRowActionRecieved() method in the subscribing component will look something like this

onRowActionRecieved($event){
    const { data } = $event;
      this.router.navigate([`${Path.DATA_DETAIL}/${data.user_id}`]);
  }

And that is how to build a reusable component using @input() and @output() decorators in Angular.

Building a reusable component using ng-content directive

Hold on.

What happens when you want to build a reusable component but in addition to passing data to the reusable component, you also want to pass your HTML markup, you want to style your data with your own HTML tags. Situations like these are when ng-content comes into play.

Let's say we want to build a card reusable component. Ng-content is used as follows

<div class="info">
    <div class="info--heading">
        <ng-content select=".cardHeading"></ng-content>
    </div>
    <div class="row">
        <ng-content></ng-content>
        <div class="col-6 col-md-4 col-xl-6 info-group">
            <ng-content select=".columnHeading"></ng-content>
            <ng-content select=".columnBody"></ng-content>
        </div>
    </div>
</div>

The ng-content can be described as a placeholder that is replaced at run time with whatever markup we have in the subscribing component. Since we have multiple placeholders, there must be a way to tell which markup goes where and that is where "select" is used. Select is used together with CSS selectors to identify the different ng-content/placeholders available.

Using this card component in the subscribing component will look like this

<card>
    <div class="cardHeading">
        <h3 class="info--heading">Truth or Dare</h3>
    </div>
    <div class="columnHeading">
        <p class="info--title">Dare</p>
    </div>
    <div class="columnBody">
        <p class="info--value">{{ dare.value }}</p>
    </div>
</card>

In the card component, we used "select" with the CSS class selector. In the subscribing component, we are consuming the ng-content/placeholder through the CSS class.

This method allows for more flexibility and the ability to provide more dynamic content to a reusable component.

Now, let's go build our reusable component!