---
title: "Angular Timeline Custom Cell Content Example | Mobiscroll"
component: "Timeline"
framework: "Angular"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/angular/timeline/dynamic-cell-content-template"
---

# Custom Cell Content - Angular Timeline demo

Add workload badges, status icons, and a hover quick-add button to each Angular Timeline cell — and cap events per cell.

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

## Description

Dynamically customize the cell content of the timeline by showing event-related icons, visual indicators, and a quick add button directly in each cell.

This is achieved using the [`cellTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/timeline#template-cellTemplate) option, where event data in each cell is used to display icons based on the event, a badge showing total event hours, and a button to quickly add new events. The add event icon is dynamically added during [`onCellHoverIn`](https://mobiscroll.com/docs/angular/eventcalendar/timeline#event-onCellHoverIn). An event cap per cell is implemented by reading the event count from [`onCellHoverIn`](https://mobiscroll.com/docs/angular/eventcalendar/timeline#event-onCellHoverIn) args and enforcing it in [`onEventCreate`](https://mobiscroll.com/docs/angular/eventcalendar/timeline#event-onEventCreate), blocking event creation by returning `false`. This approach helps visualize workload per day and enables quick actions, making scheduling more efficient and user-friendly.

In this demo, cells represent a day. Depending on the resolution, they can also stand for a week, month, or more, while remaining fully customizable. See this [configuration demo](https://demo.mobiscroll.com/angular/timeline/multiple-days-weeks-months-quarters-years-variable-resolution) to try out different timeline resolution settings.

## What this demo shows

- A desktop monthly timeline where days are arranged horizontally and resources are listed as rows on the left.
- **Header navigation** The month and year label opens date navigation, the previous and next arrows move the visible timeline range, and the Today button returns to the current date.
- **Month view** The timeline header shows the days of the selected month, with the current date highlighted.
- **Resources** Multiple resources are displayed as separate timeline rows.
- **Custom cell content** Each resource-day cell shows scheduled hours against available working hours, such as `8h`.
- **Workload indicators** Color-coded badges show resource utilization at a glance, including fully utilized, partially utilized, and overloaded or underutilized states based on the configured rules.
- **Task overview** Individual tasks remain visible beneath the workload summary, so each cell shows both daily capacity and scheduled work.
- **Dynamic summaries** Workload totals reflect the current events in the cell when event data changes.
- **Status icons** Custom icons can represent task metadata, priorities, activity types, or other event categories.
- **Event labels** Events are displayed as blue labels with the event title and duration.
- **Event creation** A `+` button appears in the upper-right corner of a cell on hover and can be used to add a new event.
- **Date positioning** Events are placed by their assigned resource and date.
- **Event interaction** Events highlight on hover.
- **Event selection** Clicking an event selects and highlights it.
- **Scrolling** The timeline supports horizontal and vertical scrolling for navigating through dates and resources.

## Best for

- **Workforce scheduling** Planning daily work across multiple people, teams, or resource groups.
- **Project planning** Comparing assigned work against available capacity by resource and day.
- **Field service scheduling** Reviewing technician or crew workload across a monthly timeline.
- **Manufacturing planning** Balancing daily tasks across machines, stations, or production resources.
- **Resource management** Spotting overbooked, underused, or partially allocated resources in a timeline layout.
- **Capacity planning** Showing workload totals and task details together without leaving the calendar view.

## 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
- [`extendDefaultEvent`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-extendDefaultEvent) - option
- [`cellTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/api#template-cellTemplate) - template
- [`clickToCreate`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-clickToCreate) - option
- [`data`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-data) - 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
- [`resources`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-resources) - option
- [`timelineEventContentTemplate`](https://mobiscroll.com/docs/angular/eventcalendar/api#template-timelineEventContentTemplate) - template
- [`view`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-view) - option

## Code

### Component

```ts
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MbscCalendarCellData, MbscCalendarEvent, MbscEventcalendarView, MbscModule, Notifications, setOptions } from '@mobiscroll/angular';

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

@Component({
  selector: 'app-root', 
  templateUrl: './app.component.html',
  standalone: true,
  imports: [CommonModule, MbscModule],
  providers: [Notifications],
})
export class AppComponent {
  constructor(private notify: Notifications) {}
  Math = Math;

  iconMap: Record<string, string> = {
    Review: 'calendar',
    Demo: 'play',
    Kickoff: 'flag',
    Strategy: 'map',
    Collab: 'bubbles',
    Update: 'upload',
    Discussion: 'bubble',
    Planning: 'pencil',
    Retrospect: 'history',
    Onboard: 'user4',
  };
  titles = Object.keys(this.iconMap);

  myView: MbscEventcalendarView = {
    timeline: {
      type: 'month',
      resolutionHorizontal: 'day',
      startDay: 1,
      endDay: 5,
      eventDisplay: 'fill',
    },
  };

  myResources = [
    { id: 1, name: 'Resource A' },
    { id: 2, name: 'Resource B' },
    { id: 3, name: 'Resource C' },
    { id: 4, name: 'Resource D' },
  ];

  myEvents: MbscCalendarEvent[] = [
    {
      id: 1,
      resource: 1,
      title: 'Review',
      start: '2026-11-02T00:00',
      end: '2026-11-02T02:00',
    },
    {
      id: 2,
      resource: 1,
      title: 'Demo',
      start: '2026-11-02T00:00',
      end: '2026-11-02T02:00',
    },
    {
      id: 3,
      resource: 1,
      title: 'Kickoff',
      start: '2026-11-02T00:00',
      end: '2026-11-02T02:00',
    },
    {
      id: 4,
      resource: 4,
      title: 'Strategy',
      start: '2026-11-02T00:00',
      end: '2026-11-02T02:00',
    },
    {
      id: 5,
      resource: 4,
      title: 'Collab',
      start: '2026-11-02T00:00',
      end: '2026-11-02T02:00',
    },
    // Showing partial data. Download full source.
  ];

  handleAddClick(ev: MouseEvent, cell: MbscCalendarCellData) {
    ev.stopPropagation();
    const count = cell.events.length;

    if (count >= 4) {
      this.notify.toast({ message: 'Task limit reached' });
      return;
    }
    this.myEvents = [
      ...this.myEvents,
      {
        title: this.titles[Math.floor(Math.random() * this.titles.length)],
        start: cell.date,
        end: new Date(cell.date.getTime() + 2 * 3600000),
        resource: cell.resource.id,
      },
    ];
  }

  getHours(events: MbscCalendarEvent[] = []): number {
    const total = events.reduce((s, ev) => {
      const st = new Date(ev.start as Date).getTime();
      const en = new Date(ev.end as Date).getTime();
      return s + (en - st) / 36e5;
    }, 0);
    return Math.round(total);
  }

  getBadgeClass(events: MbscCalendarEvent[] = []): string {
    const hours = this.getHours(events);
    const classMap: Record<number, string> = { 2: 'light', 4: 'medium', 6: 'semi', 8: 'full' };
    return 'mds-timeline-cell-content-badge-' + (classMap[hours] || 'default');
  }

  getIcons(events: MbscCalendarEvent[] = []): string[] {
    const titles = new Set();
    return events.reduce<string[]>((icons, ev) => {
      const name = this.iconMap[ev.title!];
      if (name && !titles.has(ev.title)) {
        titles.add(ev.title);
        icons.push(name);
      }
      return icons;
    }, []);
  }

  extendDefaultEvent = (args: any) => ({
    title: this.titles[Math.floor(Math.random() * this.titles.length)],
    end: new Date(args.start.getTime() + 2 * 3600000),
  });
}
```

### Template

```html
<mbsc-eventcalendar
  class="mds-timeline-cell-content"
  [cellTemplate]="cellTpl"
  [clickToCreate]="false"
  [data]="myEvents"
  [dragToCreate]="false"
  [dragToMove]="false"
  [dragToResize]="false"
  [extendDefaultEvent]="extendDefaultEvent"
  [resources]="myResources"
  [timelineEventContentTemplate]="eventContentTpl"
  [view]="myView"
>
  <ng-template #cellTpl let-cell>
    <div class="mds-timeline-cell-content-badge {{ getBadgeClass(cell.events) }}">{{ getHours(cell.events) }}h / 8h</div>

    <button class="mds-timeline-cell-content-add" (click)="handleAddClick($event, cell)">+</button>

    <div class="mds-timeline-cell-icons">
      <ng-container *ngFor="let icon of getIcons(cell.events)">
        <i class="mbsc-font-icon mds-timeline-cell-icon mbsc-icon-{{ icon }}"></i>
      </ng-container>
    </div>
  </ng-template>

  <ng-template #eventContentTpl let-event>
    <div>{{ event.title }} - {{ Math.round((event.endDate - event.startDate) / 36e5) }}h</div>
  </ng-template>
</mbsc-eventcalendar>
```

### CSS

```css
.mds-timeline-cell-content .mbsc-timeline-day-fri::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  border-right: 1px solid var(--mbsc-eventcalendar-border-color);
}

.mds-timeline-cell-content .mbsc-timeline-day-fri.mbsc-ltr::after {
  left: 100%;
}

.mds-timeline-cell-content .mbsc-timeline-day-fri.mbsc-rtl::after {
  right: 100%;
}

.mds-timeline-cell-content .mbsc-timeline-events {
  top: 25px;
}

.mds-timeline-cell-content .mbsc-timeline-row-gutter {
  height: 55px;
}

.mds-timeline-cell-content .mbsc-timeline-column {
  width: 110px;
}

.mds-timeline-cell-content-badge {
  position: relative;
  top: 6px;
  left: 4px;
  display: inline-block;
  padding: 1px 4px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 12px;
  max-width: 80px;
  color: black;
  background: #f9f9f9;
  overflow: hidden;
  z-index: 0;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}

.mds-timeline-cell-content-badge::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  transform: skewX(-15deg);
  z-index: -1;
}

.mds-timeline-cell-content-badge-light::before {
  width: 25%;
  background: #ff9c9c;
}

.mds-timeline-cell-content-badge-medium::before {
  width: 50%;
  background: #ffcf9f;
}

.mds-timeline-cell-content-badge-semi::before {
  width: 75%;
  background: #fff4a3;
}

.mds-timeline-cell-content-badge-full::before {
  width: 100%;
  background: #a3e4a1;
}

.mds-timeline-cell-content-add {
  opacity: 0;
  position: absolute;
  top: 6px;
  width: 17px;
  height: 17px;
  line-height: 13px;
  padding: 0;
  padding-bottom: 4px;
  color: #fff;
  border: none;
  border-radius: 50%;
  font-size: 22px;
  cursor: pointer;
  background: linear-gradient(135deg, rgb(73, 73, 73), rgb(22, 21, 21));
  transition: transform 0.2s ease;
}

.mbsc-timeline-column.mbsc-ltr .mds-timeline-cell-content-add {
  right: 4px;
}

.mbsc-timeline-column.mbsc-rtl .mds-timeline-cell-content-add {
  left: 4px;
}

.mbsc-timeline-column:hover .mds-timeline-cell-content-add {
  opacity: 1;
  pointer-events: auto;
}

.mds-timeline-cell-content-add:hover {
  outline: 3px solid rgba(140, 140, 140, 0.5);
}

.mds-timeline-cell-icons {
  position: absolute;
  inset: auto auto 4px auto;
}

.mds-timeline-cell-icon {
  text-align: center;
  padding: 2px 4px;
  margin: 0 3px;
  line-height: 17px !important;
  color: #000;
  background: rgba(255, 255, 255, 0.8);
  border-radius: 5px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  font-size: 13px;
}
```

## Implementation notes

- Use `timeline: { type: 'month', resolutionHorizontal: 'day', startDay: 1, endDay: 5, eventDisplay: 'fill' }` - a monthly timeline with one column per day, Mon-Fri only, events listed as fills rather than positioned by time.
- Define 4 plain resources (Resource A-D) with only `id` and `name` - no `color` needed. Events have no `color` either; the cell badge provides the workload indicator instead.
- Build an `iconMap` that maps event title strings to Mobiscroll icon names (e.g. `Review → 'calendar'`, `Demo → 'play'`, `Kickoff → 'flag'`). Use the keys of this map as the pool of valid event titles.
- Use `renderCell` (Angular: `cellTemplate`, Vue: `#cell`) to inject content into each timeline cell. The render function receives a `cell` object with `cell.events` (the events on that cell), `cell.date`, and `cell.resource`. From `cell.events`, compute the total scheduled hours by summing each event's duration, and render: a workload badge showing `{hours}h / 8h`, a "+" add button, and a row of deduplicated event-type icons (one icon per distinct title using `mbsc-icon-{iconName}`).
- Use `renderTimelineEventContent` (Angular: `timelineEventContentTemplate`, Vue: `#timelineEventContent`) to render a compact event label showing the event title and its duration in hours, calculated from `event.endDate` and `event.startDate`.
- In the "+" button's click handler, read `cell.events.length`. If it is 4 or more, show a `Toast` with "Task limit reached" and return early. Otherwise, add a new event to the events array with `start: cell.date`, `end: 2 hours later`, `resource: cell.resource.id`, and a random title from the titles pool. In Angular, call `stopPropagation()` on the click event to prevent the calendar from handling it.
- Use `extendDefaultEvent` returning a random title from the titles pool and an `end` 2 hours after `args.start`, so the same defaults apply to any events created via other means.
- Disable all built-in interaction: set `clickToCreate`, `dragToCreate`, `dragToMove`, and `dragToResize` all to `false`.

## 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: [Event Appearance & Time Buffers](https://demo.mobiscroll.com/angular/timeline/timeline-custom-event-rendering.md) - Customize event layouts on a Angular Timeline with icons, labels, and colors — visualize before/after buffers for travel, prep, or inspection workflows.
- Next: [Header and Footer Template](https://demo.mobiscroll.com/angular/timeline/hour-day-week-month-quarter-year-header-footer-template.md) - Customize time-scale headers and add occupancy footers to a Angular Timeline — format cells from hour to year and display per-slot utilization.
