External Resource Drag & Drop

By combining the resourceReorder feature with external resource drag-and-drop, you can easily organize, group, and adjust resources by dragging them into or out of the timeline view.

Dragging resources onto the timeline

Resources can be created by dragging and dropping an external element onto the timeline. In order for that to work you will need to have two things set up:

  • Enable the timeline to receive external resources by setting externalResourceDrop to true.
  • Initialize the external resources (containers) as draggable components.

Set up a container as draggable with the mbsc-draggable directive. Pass a skeleton event definition through the dragData option which will be added to the event calendar on drop. If omitted, a default event will be created.

Use the onResourceCreate for triggering a custom logic on drop like showing a toast.

Dragging resources off the timeline)

Additionally resource can be removed by dragging them out from the timeline and dropping them onto an external drop container. To activate this, you will need to:

  • Enable the timeline to allow dragging events out by setting externalResourceDrag to true.
  • Initialize the

dropcontainer.

Resources can also be dropped on another timeline with externalResourceDrop enabled.

When a resource is dropped into an external drop container or another timeline, it will be deleted from the original calendar and the onResourceDelete will be fired.

You can also use the onResourceDragLeave and onResourceDragEnter, and the drop container's onItemDragEnter and onItemDragLeave events to provide visual feedback or running custom logic during drag.

Looking for external event drag & drop?  Check out this example →
Get started with this example

Demo

Loading the live demo

Preview in full screen

Drag external resources onto a Timeline to add team members, or drag them off to remove them — build crew scheduling and team management.

Code

import { CommonModule } from '@angular/common';
import { Component, ElementRef, QueryList, ViewChild, ViewChildren } from '@angular/core';
import {
  MbscCalendarEvent,
  MbscEventcalendar,
  MbscEventcalendarView,
  MbscItemDragEvent,
  MbscModule,
  MbscResource,
  MbscResourceCreateEvent,
  MbscResourceDeleteEvent,
  MbscResourceOrderEvent,
  Notifications,
  setOptions ,
} from '@mobiscroll/angular';

setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
});

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

  @ViewChild('timeline', { static: true })
  timelineInst!: MbscEventcalendar;

  @ViewChildren('draggables')
  dragElements!: QueryList<ElementRef<HTMLDivElement>>;

  installers: MbscResource[] = [
    {
      id: 'it-1',
      name: 'Installer team 1',
      eventCreation: false,
      reorder: false,
      children: [
        {
          id: 1,
          name: 'Emily Carter',
          color: '#007acc',
          title: 'Electrician',
        },
        {
          id: 2,
          name: 'Michael Lawson',
          color: '#008000',
          title: 'Plumber',
        },
      ],
    },
    // Showing partial data. Download full source.
  ];

  availableInstallers: MbscResource[] = [
    {
      id: 9,
      name: 'Adam Miller',
      color: '#C70039',
      title: 'Mason',
    },
    {
      id: 10,
      name: 'Isabella Martinez',
      color: '#2ECC71',
      title: 'Surveyor',
    },
    {
      id: 11,
      name: 'Mark White',
      color: '#34495E',
      title: 'Glazier',
    },
    // Showing partial data. Download full source.
  ];

  tasks: MbscCalendarEvent[] = [
    {
      id: 1,
      start: '2026-10-21T07:00',
      end: '2026-10-21T13:00',
      title: 'Electrical wiring installation',
      resource: 1,
    },
    {
      id: 2,
      start: '2026-10-21T14:00',
      end: '2026-10-21T18:00',
      title: 'Power panel connection',
      resource: 1,
    },
    {
      id: 3,
      start: '2026-10-21T08:00',
      end: '2026-10-21T12:00',
      title: 'Water pipe fitting',
      resource: 2,
    },
    // Showing partial data. Download full source.
  ];

  myView: MbscEventcalendarView = {
    timeline: { type: 'day', resourceReorder: true, startTime: '07:00', endTime: '18:00' },
  };

  onResourceCreate(args: MbscResourceCreateEvent) {
    this.availableInstallers = this.availableInstallers.filter((installer) => installer.id !== args.resource.id);
    this.notify.toast({
      message: args.resource.name + ' added to ' + args.parent?.name,
    });
  }

  onResourceDelete(args: MbscResourceDeleteEvent) {
    this.notify.toast({
      message: args.resource.name + ' removed from ' + args.parent?.name,
    });
  }

  onResourceOrderUpdate(args: MbscResourceOrderEvent) {
    const parent = args.parent;
    const oldParent = args.oldParent;

    if (parent && oldParent) {
      this.notify.toast({
        message: args.resource.name + ' moved to ' + args.parent?.name,
      });
    }

    if (parent && parent.children) {
      // Remove placeholder resource
      parent.children = parent.children.filter((child) => !child['placeholder']);
    }

    if (oldParent && oldParent.children && !oldParent.children.length) {
      // Add placeholder resource
      oldParent.children.push({
        id: 'ph-' + oldParent.id,
        name: 'Drag Technicians here',
        placeholder: true,
        reorder: false,
      });
    }
  }

  onItemDrop(args: MbscItemDragEvent): void {
    if (args.data && args.dataType === 'resource') {
      this.availableInstallers = [...this.availableInstallers, args.data as MbscResource];
      setTimeout(() => {
        const dragElms = this.dragElements;
        const lastDragElm = dragElms.get(dragElms.length - 1);
        if (lastDragElm) {
          lastDragElm.nativeElement.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest',
          });
        }
      });
    }
  }

  addNewTeam() {
    const teamLength = this.installers.length + 1;
    const resourceId = 'it-' + teamLength;

    this.installers = [
      ...this.installers,
      {
        id: resourceId,
        eventCreation: false,
        reorder: false,
        name: 'Installer team ' + teamLength,
        children: [
          {
            id: 'ph-' + resourceId,
            name: 'Drag Technicians here',
            placeholder: true,
            reorder: false,
          },
        ],
      },
    ];

    this.timelineInst.navigateToEvent({
      start: new Date(),
      resource: resourceId,
    });
  }
}

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.