Create a timeline flow component with Angular

 

I work for a Tech company in the Netherlands, currently working on assignments for a client in the Banking/Insurance domain. Recently we came across a scenario where we wanted to show the status of different processes to the customer. In the current situation, the customer if wants to know the status has to contact the customer care. We wanted to reduce those calls and hence decided to show the statuses of these process in the customer’s account.
We ended up creating something like this:
You can pass the list of items displayed and also required configuration as input, making the component completely customizable.
In this tutorial, I am going to create a new Angular CLI project and then create the component. The Source Code and Live Demo of this tutorial are available on Github.
So, let’s get started.

Step 1: Create a new Angular CLI project

ng new timeline-demo --style scss

Step 2: Create the timeline component

Angular CLI will create the project and install the dependencies for you. Once done, navigate to the project directory and create a new component
cd timeline-demo
ng generate component timeline-flow
At this point, you have a brand new angular CLI project created with 2 components in it – the AppComponent and TimelineFlowComponent.

Step 3: Add the template and logic for our timeline component

  • timeline-flow.component.html

<div class="timeline-flow-wrapper">

<ul class="step-progress"> 

<li class="step-progress-item list-edge" *ngIf="options.showTopEdge">
      <span class="connector"></span>
      <span class="status-text"></span>
    </li>


<li *ngFor="let step of data; let i =index;" class="step-progress-item" [ngClass]="{'list-edge': i === data.length - 1 && options.showBottomEdge}">
      <span class="connector" [ngStyle]="getStyles(step, 'connector-styles')"></span>
      <span class="status-text" [ngStyle]="getStyles(step, 'text-styles')">{{step.step}}</span>
      <span class="status-icon" [ngClass]="getStyles(step, 'icon-class')" [ngStyle]="getStyles(step, 'icon-styles')"></span>
    </li>

  </ul>

</div>

  • timeline-flow.component.scss
$contrastGrey: #c8c8c8;

.timeline-flow-wrapper {
  font-size: 1.4em;
  padding: 15px;
  .step-progress {
    position: relative;
    top: 10px;
    padding-left: 40px;
    list-style: none;
    margin: 0;

    &::before {
      display: inline-block;
      content: '';
      position: absolute;
      top: 0;
      left: 15px;
      width: 10px;
      height: 100%;
    }

    &-item {
      position: relative;

      .status-text {
        position: relative;
        top: -11px;
      }

      &:not(:last-child), &.list-edge {
        padding-bottom: 40px;
        .connector {
          border-left: 10px solid $contrastGrey;
        }
      }
      &.list-edge {
        padding-bottom: 10px !important;
      }

      .connector {
        display: inline-block;
        content: '';
        position: absolute;
        left: -30px;
        height: 100%;
        width: 10px;
      }

      .status-icon {
        content: '';
        display: inline-block;
        position: absolute;
        top: -11px;
        width: 22px;
        height: 22px;
        left: -37px;
        text-align: center;
        border-radius: 50%;
        background-color: white;
      }
    }
  }
}

  • timeline-flow.component.ts
import { Component, Input } from '@angular/core';
import { DEFAULT_STATUS_VALUES, TimelineFlowOptions, StepsData } from './timeline-flow.options';

@Component({
  selector: 'app-timeline-flow',
  templateUrl: './timeline-flow.component.html',
  styleUrls: ['./timeline-flow.component.scss']
})
export class TimelineFlowComponent {

  @Input() data: StepsData[] = [];
  @Input() options: TimelineFlowOptions = DEFAULT_STATUS_VALUES;

  constructor() {
  }

  getStyles(step, type) {
    const statusRec: any = this.options.statuses.find(item => item.text === step.status);
    const styles = {};
    if (statusRec && statusRec.styles) {
      switch (type) {
        case 'icon-styles':
          if (statusRec.styles.iconPath) {
            styles['content'] = `url(${statusRec.styles.iconPath})`;
          }
          if (statusRec.styles.textColor) {
            styles['color'] = statusRec.styles.textColor;
          }
          styles['font-weight'] = 'bold';
          break;
        case 'text-styles':
          if (statusRec.styles.textColor) {
            styles['color'] = statusRec.styles.textColor;
          }
          break;
        case 'icon-class':
          if (statusRec.styles.iconClass) {
            styles[statusRec.styles.iconClass] = true;
          }
          break;
        case 'connector-styles':
          if (statusRec.styles.borderColor) {
            styles['border-left-color'] = statusRec.styles.borderColor;
          }
          break;
      }
    }
    return styles;
  }
}
  • timeline-flow.options.ts
export const DEFAULT_STATUS_VALUES = {
  statuses: [{
    text: 'Not Started',
    styles: {borderColor: '', textColor: '#A5A5A5', iconClass: 'fa fa-circle-o', iconPath: ''}
  }, {
    text: 'In progress',
    styles: {borderColor: '', textColor: '#0077C8', iconClass: 'fa fa-exclamation-circle', iconPath: ''}
  }, {
    text: 'Completed',
    styles: {borderColor: '#0077C8', textColor: '#49AF57', iconClass: 'fa fa-check-circle', iconPath: ''}
  }],
  showTopEdge: false,
  showBottomEdge: false
};

export interface TimelineFlowOptions {
  statuses: StatusConfig[];
  showTopEdge?: boolean;
  showBottomEdge?: boolean;
}

interface StatusConfig {
  text: string;
  styles?: StatusConfigStyles;
}

interface StatusConfigStyles {
  borderColor?: string;
  textColor?: string;
  iconClass?: string;
  iconPath?: string;
}

export interface StepsData {
  step: string;
  status: string;
}
As you can see here, you can completely customize the timeline component by passing the config/options as Input. In this case, I am using Font Awesome for the icons and hence you see font awesome classes in the config. You can use any other library or use your own font classes if you’ve any. You can also pass the font path as part of the config in case you don’t have a font class

Step 4: Invoke the timeline component from AppComponent

  • app.component.html
<app-timeline-flow [data]="statusData"></app-timeline-flow>
  • app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  statusData: any[] = [{
    step: 'Step 1',
    status: 'Completed'
  }, {
    step: 'Step 2',
    status: 'Completed'
  }, {
    step: 'Step 3',
    status: 'Completed'
  }, {
    step: 'Step 4',
    status: 'In progress'
  }, {
    step: 'Step 5',
    status: 'Not Started'
  }, {
    step: 'Step 6',
    status: 'Not Started'
  }];
}
And here we have, our own timeline component. You can change the styling as per your requirements. As mentioned above, the source code is available on Github. You can see the live demo of this component Here
Thank you for your time. Please leave a comment and share this tutorial if you liked it. Also, leave a comment if you want something improved and I’ll try to make that happen.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.