---
title: "Angular Timeline Task Progress Tracking Example | Mobiscroll"
component: "Timeline"
framework: "Angular"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/angular/timeline/show-task-progress-on-event"
---

# Task Progress Tracking - Angular Timeline demo

Show task completion progress on Angular Timeline events with an inline hover slider for live updates and a progress field in the add/edit dialog.

- [Live demo](https://demo.mobiscroll.com/angular/timeline/show-task-progress-on-event)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-angular/tree/main/src/demos/eventcalendar/timeline/show-task-progress-on-event)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/timeline/show-task-progress-on-event.md), [jQuery](https://demo.mobiscroll.com/jquery/timeline/show-task-progress-on-event.md), [React](https://demo.mobiscroll.com/react/timeline/show-task-progress-on-event.md), [Vue](https://demo.mobiscroll.com/vue/timeline/show-task-progress-on-event.md)

## Description

Showing percentage progress, or even having a visual overlay that indicates the progress of a task is sometimes necessary, not just for full fledged Gantt charts. Although being usually associated with Gantt charts, having a clear overview of progress with tools that enable live update is useful in a lot of situations.

Using the event templating capabilities, it is possible to render a live slider that reacts on hover and enables updating the progress inline. Use the [`timelineEventTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/timeline#template-timelineEventTemplate) option to customize the template and adjust it to your needs.

Besides being able to update progress right on the event, it is also possible to add a slider that controls task progression - saved in a custom property of the event - inside an add/edit dialog.

## What this demo shows

- A desktop monthly timeline for project planning and progress tracking, with days arranged horizontally and resources listed vertically on the left.
- **Header navigation** The month and year label opens date navigation. The previous and next arrows and the Today button move between months and return to the current day.
- **Timeline header** The fixed date strip shows the days of the selected month in `D DDD` format, with the current date highlighted.
- **Resources** Hierarchical resource groups organize team members into departments such as Designer Team, Development Team, and QA Team.
- **Resource details** Individual resources show custom information, including the team member's role or job title.
- **Event colors** Events for the same team member or group keep a consistent color in the timeline.
- **Event cards** Events are rendered as colored cards with the project name, a progress percentage, and an inline progress slider.
- **Inline progress** The progress slider reacts on hover and lets users update completion directly on the event.
- **Event positioning** Events are placed by assigned resource and by start and end date.
- **Event creation** Users can create events by double-clicking a resource row or by clicking and dragging across a date range.
- **Add form** Creating an event opens a form with title, start date, end date, and progress slider fields, plus Cancel and Add actions.
- **Event editing** Clicking an event selects it and opens a popup for modifying the event details.
- **Drag and resize** Events show drag and resize handles on hover for moving tasks or changing their duration.
- **Vertical scrolling** The resource area scrolls vertically so the timeline can display more resources in the same view.

## Best for

- **Project management** Tracking tasks, ownership, timing, and completion progress in a single timeline.
- **Sprint planning** Planning work across team members and date ranges while keeping progress visible.
- **Software development teams** Coordinating designers, developers, and QA resources across overlapping project tasks.
- **Task tracking** Updating task progress directly from the timeline without opening a separate detail view.
- **Team workload management** Reviewing who is assigned to what, when each task runs, and how far along the work is.
- **Gantt-style planning** Showing project spans and completion percentages without requiring a full Gantt chart implementation.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-themeVariant) - option
- [`clickToCreate`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToResize) - option
- [`view`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-view) - option
- [`onEventClick`](https://mobiscroll.com/docs/angular/eventcalendar/api#event-onEventClick) - event
- [`onEventCreated`](https://mobiscroll.com/docs/angular/eventcalendar/api#event-onEventCreated) - event
- [`cssClass`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-cssClass) - option
- [`display`](https://mobiscroll.com/docs/angular/popup/api#opt-display) - option
- [`fullScreen`](https://mobiscroll.com/docs/angular/popup/api#opt-fullScreen) - option
- [`onClose`](https://mobiscroll.com/docs/angular/popup/api#event-onClose) - event
- [`responsive`](https://mobiscroll.com/docs/angular/popup/api#opt-responsive) - option
- [`width`](https://mobiscroll.com/docs/angular/popup/api#opt-width) - option
- [`touchUi`](https://mobiscroll.com/docs/angular/popup/api#opt-touchUi) - option
- [`controls`](https://mobiscroll.com/docs/angular/datepicker/api#opt-controls) - option
- [`touchUi`](https://mobiscroll.com/docs/angular/datepicker/api#opt-touchUi) - option
- [`select`](https://mobiscroll.com/docs/angular/datepicker/api#opt-select) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/angular/datepicker/api#opt-showRangeLabels) - option
- [`data`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-resources) - option
- [`resourceTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/api#template-resourceTemplate) - template
- [`timelineEventTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/api#template-timelineEventTemplate) - template
- [`anchor`](https://mobiscroll.com/docs/angular/popup/api#opt-anchor) - option
- [`buttons`](https://mobiscroll.com/docs/angular/popup/api#opt-buttons) - option
- [`headerText`](https://mobiscroll.com/docs/angular/popup/api#opt-headerText) - option
- [`responsive`](https://mobiscroll.com/docs/angular/datepicker/api#opt-responsive) - option
- [`startInput`](https://mobiscroll.com/docs/angular/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/angular/datepicker/api#opt-endInput) - option

## Code

### Component

```ts
import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
  MbscCalendarEvent,
  MbscDatepickerOptions,
  MbscDateType,
  MbscEventcalendarOptions,
  MbscModule,
  MbscPopup,
  MbscPopupButton,
  MbscPopupOptions,
  setOptions,
} from '@mobiscroll/angular';

setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

@Component({
  selector: 'app-root', 
  templateUrl: './app.component.html',
  standalone: true,
  imports: [CommonModule, FormsModule, MbscModule],
})
export class AppComponent {
  @ViewChild('popup', { static: false })
  popup!: MbscPopup;

  popupEventTitle: string | undefined;
  popupEventProgress = 0;
  popupEventResource: string | undefined;
  popupEventDates: (MbscDateType | undefined)[] = [];

  isDraggingProgress = false;

  myEvents: MbscCalendarEvent[] = [
    {
      start: '2026-10-22T00:00',
      end: '2026-10-25T00:00',
      title: 'Design Homepage',
      resource: 'alice',
      progress: 100,
    },
    {
      start: '2026-10-22T00:00',
      end: '2026-10-26T00:00',
      title: 'Create Wireframes',
      resource: 'bob',
      progress: 100,
    },
    {
      start: '2026-10-24T00:00',
      end: '2026-10-29T00:00',
      title: 'Develop Frontend',
      resource: 'charlie',
      progress: 45,
    },
    {
      start: '2026-10-24T00:00',
      end: '2026-10-29T00:00',
      title: 'Develop Backend',
      resource: 'dave',
      progress: 35,
    },
    {
      start: '2026-10-29T00:00',
      end: '2026-11-02T00:00',
      title: 'Test Website',
      resource: 'erin',
      progress: 0,
    },
    {
      start: '2026-10-26T00:00',
      end: '2026-11-02T00:00',
      title: 'Fix Bugs',
      resource: 'frank',
      progress: 0,
    },
    {
      start: '2026-11-02T00:00',
      end: '2026-11-05T00:00',
      title: 'Deploy Website',
      resource: 'george',
      progress: 0,
    },
  ];

  myResources = [
    {
      id: 'gro1',
      name: 'Designer Team',
      color: '#76e083',
      eventCreation: false,
      children: [
        {
          id: 'alice',
          name: 'Alice',
          title: 'Designer',
          color: '#1dab2f',
        },
        {
          id: 'bob',
          name: 'Bob',
          title: 'Designer',
          color: '#76e083',
        },
      ],
    },
    {
      id: 'gro2',
      name: 'Development Team',
      color: '#ff1717',
      eventCreation: false,
      children: [
        {
          id: 'charlie',
          name: 'Charlie',
          title: 'Frontend Developer',
          color: '#4981d6',
        },
        {
          id: 'dave',
          name: 'Dave',
          title: 'Backend Developer',
          color: '#f7961e',
        },
        {
          id: 'frank',
          name: 'Frank',
          title: 'Full-Stack Developer',
          color: '#34c8e0',
        },
        {
          id: 'george',
          name: 'George',
          title: 'DevOps Engineer',
          color: '#e25dd2',
        },
      ],
    },

    {
      id: 'gro3',
      name: 'QA Team',
      color: '#d6d145',
      eventCreation: false,
      children: [
        {
          id: 'erin',
          name: 'Erin',
          title: 'QA Tester',
          color: '#d6d145',
        },
      ],
    },
  ];

  calendarOptions: MbscEventcalendarOptions = {
    clickToCreate: true,
    dragToCreate: true,
    dragToMove: true,
    dragToResize: true,
    view: {
      timeline: { type: 'month', eventDisplay: 'fill' },
    },
    onEventClick: (args) => {
      if (this.isDraggingProgress) return;
      this.isEdit = true;
      this.tempEvent = args.event;
      // Fill popup form with event data
      this.loadPopupForm(args.event);
      // Set popup options
      this.popupHeaderText = 'Edit event';
      this.popupButtons = this.popupEditButtons;
      this.popupAnchor = args.domEvent.currentTarget;
      // Open the popup
      this.popup.open();
    },
    onEventCreated: (args) => {
      setTimeout(() => {
        this.isEdit = false;
        this.tempEvent = args.event;
        // Fill popup form with event data
        this.loadPopupForm(args.event);
        // Set popup options
        this.popupHeaderText = 'New Event';
        this.popupButtons = this.popupAddButtons;
        this.popupAnchor = args.target;
        // Open the popup
        this.popup.open();
      });
    },
  };

  popupHeaderText!: string;
  popupAnchor: HTMLElement | undefined;
  popupAddButtons: (MbscPopupButton | 'cancel')[] = [
    'cancel',
    {
      handler: () => {
        this.saveEvent();
      },
      keyCode: 'enter',
      text: 'Add',
      cssClass: 'mbsc-popup-button-primary',
    },
  ];
  popupEditButtons: (MbscPopupButton | 'cancel')[] = [
    'cancel',
    {
      handler: () => {
        this.saveEvent();
      },
      keyCode: 'enter',
      text: 'Save',
      cssClass: 'mbsc-popup-button-primary',
    },
  ];
  popupButtons: (MbscPopupButton | 'cancel')[] = [];
  popupOptions: MbscPopupOptions = {
    display: 'bottom',
    contentPadding: false,
    fullScreen: true,
    onClose: () => {
      if (!this.isEdit) {
        // Refresh the list, if add popup was canceled, to remove the temporary event
        this.myEvents = [...this.myEvents];
      }
    },
    responsive: {
      medium: {
        display: 'anchored',
        width: 400,
        fullScreen: false,
        touchUi: false,
      },
    },
  };

  datePickerResponsive: Record<string, MbscDatepickerOptions> = {
    medium: {
      controls: ['calendar'],
      touchUi: false,
    },
  };
  datePickerOptions: MbscDatepickerOptions = {
    select: 'range',
    showRangeLabels: false,
    touchUi: true,
  };

  isEdit = false;

  tempEvent!: MbscCalendarEvent;

  handleProgressArrowMouseDown(e: MouseEvent, event: MbscCalendarEvent) {
    e.stopPropagation();

    this.isDraggingProgress = true;

    const progressArrow = e.target as HTMLElement;
    const progressBar = progressArrow.closest('.mds-progress-bar') as HTMLElement;
    const eventContainerWidth = progressBar.parentElement!.offsetWidth;
    const initialMouseX = e.pageX;
    const initialProgress = +progressBar.style.width.replace('%', '');

    let newProgress: number;

    const handleMouseMove = (e: MouseEvent) => {
      const mouseXOffset = e.pageX - initialMouseX;

      newProgress = Math.round(initialProgress + (mouseXOffset / eventContainerWidth) * 100);
      newProgress = Math.max(0, Math.min(100, newProgress));

      event['progress'] = newProgress;
    };

    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);

      setTimeout(() => (this.isDraggingProgress = false), 100);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  }

  loadPopupForm(event: MbscCalendarEvent) {
    this.popupEventTitle = event.title;
    this.popupEventDates = [event.start, event.end];
    this.popupEventResource = event['resource'] as string;
    this.popupEventProgress = event['progress'] || 0;
  }

  saveEvent() {
    this.tempEvent.title = this.popupEventTitle;
    this.tempEvent.start = this.popupEventDates[0];
    this.tempEvent.end = this.popupEventDates[1];
    this.tempEvent.resource = this.popupEventResource;
    this.tempEvent['progress'] = +this.popupEventProgress;

    if (this.isEdit) {
      // Update the event in the list
      this.myEvents = [...this.myEvents];
    } else {
      // Add the new event to the list
      this.myEvents = [...this.myEvents, this.tempEvent];
    }
    // Close the popup
    this.popup.close();
  }
}
```

### Template

```html
<mbsc-eventcalendar
  [data]="myEvents"
  [resources]="myResources"
  [resourceTemplate]="customResourceTemplate"
  [timelineEventTemplate]="customEventTemplate"
  [options]="calendarOptions"
>
</mbsc-eventcalendar>

<ng-template #customResourceTemplate let-resource>
  <div>
    <div class="mds-progress-employee-name">{{resource.name}}</div>
    <div *ngIf="resource.title" class="mds-progress-employee-title">{{resource.title}}</div>
  </div>
</ng-template>

<ng-template #customEventTemplate let-event>
  <div class="mds-progress-event" [style.background]="event.color">
    <div class="mds-progress-bar" [style.width]="(event.original.progress || 0) + '%'">
      <div class="mds-progress-arrow" (mousedown)="handleProgressArrowMouseDown($event, event.original)"></div>
    </div>
    <div class="mds-progress-event-content">
      <div class="mds-progress-event-title">{{event.original.title}}</div>
    </div>
    <div class="mds-progress-label">{{event.original.progress || 0}}%</div>
  </div>
</ng-template>

<mbsc-popup [options]="popupOptions" [anchor]="popupAnchor" [buttons]="popupButtons" [headerText]="popupHeaderText" #popup>
  <div class="mbsc-form-group">
    <mbsc-input label="Title" [(ngModel)]="popupEventTitle"></mbsc-input>
  </div>
  <div class="mbsc-form-group">
    <mbsc-datepicker
      [(ngModel)]="popupEventDates"
      [options]="datePickerOptions"
      [responsive]="datePickerResponsive"
      [startInput]="startInput"
      [endInput]="endInput"
    ></mbsc-datepicker>
    <mbsc-input #startInput label="Starts"></mbsc-input>
    <mbsc-input #endInput label="Ends"></mbsc-input>
  </div>
  <div class="mbsc-form-group">
    <label class="mbsc-flex mbsc-align-items-center mbsc-padding">
      <span>Progress</span>
      <input class="mds-popup-progress-slider mbsc-flex-1-0" type="range" min="0" max="100" [(ngModel)]="popupEventProgress" />
      <span class="mds-popup-progress-label">{{ popupEventProgress }}%</span>
    </label>
  </div>
</mbsc-popup>
```

### CSS

```css
.mds-progress-calendar .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-progress-calendar .mbsc-timeline-parent {
  height: 34px;
}

.mds-popup-progress-slider {
  margin: 0 10px;
}

.mds-popup-progress-label {
  margin-left: 10px;
  width: 50px;
  text-align: right;
}

.mds-progress-event {
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  line-height: 19px;
}

.mds-progress-event-content {
  padding: 10px 0;
  margin-right: 50px;
}

.mds-progress-event-title {
  position: relative;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.mds-progress-bar {
  position: absolute;
  top: 0;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
}

.mds-progress-arrow {
  position: absolute;
  right: -9px;
  bottom: 0;
  border-style: solid;
  border-width: 0 9px 9px 9px;
  border-color: transparent transparent rgba(255, 255, 255, 0.5) transparent;
  cursor: ew-resize;
}

.mds-progress-dragging {
  background-color: rgba(0, 0, 0, 0.5);
}

.mds-progress-dragging .mds-progress-arrow,
.mds-progress-arrow:hover {
  right: -12px;
  border-width: 0 12px 12px 12px;
  border-color: transparent transparent white transparent;
}

.mds-progress-label {
  position: absolute;
  right: 5px;
  top: 50%;
  margin-top: -10px;
  color: white;
  font-weight: bold;
  font-size: 14px;
}

.mds-progress-employee-name {
  font-size: 14px;
}

.mds-progress-employee-title {
  font-size: 12px;
  font-weight: 400;
  margin-top: 5px;
}
```

## Implementation notes

- Use `type: 'month'` with `eventDisplay: 'fill'` for a month-level Gantt-style view where tasks span multiple days.
- Give each event a custom `progress` property (0-100) representing task completion percentage.
- Define resources as a two-level tree of team groups (`eventCreation: false`) and individual members with a custom `title` (job role). Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to show the member name and role beneath it.
- Use `renderTimelineEvent` (Angular: `timelineEventTemplate`, Vue: `#timelineEvent` slot) to render the progress bar layout: a container styled with the resource color, a progress fill div with `width` set to `${progress}%`, a draggable arrow handle element with a `data-event-id` attribute, the task title, and a percentage label.
- Implement inline progress dragging by attaching `onMouseDownCapture` on the calendar wrapper. On mousedown on the arrow handle, call `stopPropagation` to prevent Mobiscroll from treating it as an event click or drag, then attach `mousemove`/`mouseup` listeners to the document. On mousemove, compute new progress from the x-offset relative to the event container width. On mouseup, write the new value to the event in state and remove the listeners.
- Track drag state with an `isDraggingProgress` ref. In `onEventClick`, return early if `isDraggingProgress.current` is true so the edit popup does not open when the user finishes dragging the handle. Use a short `setTimeout` before resetting the ref to prevent the mouseup from immediately triggering a click.
- Enable `clickToCreate: true` and `dragToCreate: true` for event creation. Use `onEventCreated` to open the add popup with `args.target` as the anchor. Use `onEventClick` to open the edit popup with `args.domEvent.target` as the anchor. Use `onEventUpdated` to persist drag-to-move and drag-to-resize changes.
- Build the popup with `Popup`; default to full-screen bottom sheet on mobile and switch to an anchored 400px popup at the medium breakpoint.
- Include a `Datepicker` in range mode with `startInput`/`endInput` refs for editing the task date range, and a native `<input type="range">` (0-100) for updating the progress value directly in the form.
- On popup close when the add form is canceled, refresh the events list to remove the temporary placeholder event.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-angular.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config angular
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   ng serve
   ```

On a trial, add `--trial` to the config command: `mobiscroll config angular --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/angular/getting-started/installation

## Related demos

- Previous: [Dual Synchronized Flight Scheduling](https://demo.mobiscroll.com/angular/timeline/flight-scheduling-two-synchronized-timelines.md) - Use synchronized Angular Timeline views to build a flight scheduling UI — drag flights to assign them to aircraft, with fixed event times and conflict detection.
- Next: [Shifts and Tasks](https://demo.mobiscroll.com/angular/timeline/tasks-subtasks-under-shifts.md) - Display shifts and linked tasks on a Angular Timeline — group tasks under parent shifts and restrict movement to each shift's time range.
