Appointment Scheduling

The scheduler can be set up for scheduling/unscheduling booked appointments. Simply set up a daily schedule view with multiple resources representing dentists or doctors. Next to the calendar you can render a list of unscheduled appointments.

Enabling advanced validation and combining that with the drag & drop between the calendar and external list on a resource to resource basis you got a pretty nice working base for appointment scheduling that can be further customized.

Get started with this example

Demo

Loading the live demo

Preview in full screen

Build a appointment scheduler — assign doctors or dentists, drag from an unscheduled queue, and prevent double-booking.

Code

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import {
  MbscCalendarColor,
  MbscCalendarEvent,
  MbscEventcalendarOptions,
  MbscModule,
  Notifications,
  setOptions ,
} from '@mobiscroll/angular';

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

type MyEvent = MbscCalendarEvent & { job?: string; unscheduled?: boolean };

const now = new Date();
const today = new Date(now.setMinutes(59));
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);

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

  myData: MyEvent[] = [
    {
      id: 'job1',
      start: '2026-10-25T14:00',
      end: '2026-10-25T16:00',
      resource: 1,
      title: 'Myla Bennett',
      job: 'Wisdom tooth removal',
      color: '#334ab9',
    },
    {
      id: 'job2',
      start: '2026-10-25T17:00',
      end: '2026-10-25T18:30',
      resource: 1,
      title: 'Beatrix Foley',
      job: 'Braces',
      color: '#177e70',
    },
    {
      id: 'job3',
      start: '2026-10-25T08:00',
      end: '2026-10-25T09:30',
      resource: 3,
      title: 'Frank Watson',
      job: 'Teeth whitening',
      color: '#d1891f',
    },
    {
      id: 'job4',
      start: '2026-10-25T10:00',
      end: '2026-10-25T12:30',
      resource: 3,
      title: 'Jaime Joyce',
      job: 'Root canal treatment',
      color: '#cb3939',
    },
    {
      id: 'job5',
      start: '2026-10-25T13:00',
      end: '2026-10-25T14:00',
      resource: 3,
      title: 'Corey Shepard',
      job: 'Tooth extraction',
      color: '#aba343',
    },
    {
      id: 'job6',
      start: '2026-10-25T14:00',
      end: '2026-10-25T16:00',
      resource: 4,
      title: 'Callie Leonard',
      job: 'Crown and bridge',
      color: '#1ca11a',
    },
    {
      id: 'job7',
      start: '2026-10-25T17:00',
      end: '2026-10-25T18:00',
      resource: 4,
      title: 'Harley Thomson',
      job: 'Tartar removal',
      color: '#a446b5',
    },
    {
      id: 'job8',
      start: '2026-10-25T09:00',
      end: '2026-10-25T11:00',
      resource: 6,
      title: 'Ricky Welch',
      job: 'Wisdom tooth removal',
      color: '#334ab9',
    },
  ];

  appointments: MyEvent[] = [
    {
      id: 'd1',
      title: 'Winfred Lesley',
      job: 'Teeth whitening',
      color: '#d1891f',
      start: '2026-10-25T08:00',
      end: '2026-10-25T09:30',
      unscheduled: true,
    },
    {
      id: 'd2',
      title: 'Rosalin Delice',
      job: 'Crown and bridge',
      color: '#1ca11a',
      start: '2026-10-25T08:00',
      end: '2026-10-25T10:00',
      unscheduled: true,
    },
    {
      id: 'd3',
      title: 'Macy Steven',
      job: 'Root canal treatment',
      color: '#cb3939',
      start: '2026-10-25T10:00',
      end: '2026-10-25T12:30',
      unscheduled: true,
    },
    {
      id: 'd4',
      title: 'Lavern Cameron',
      job: 'Tartar removal',
      color: '#a446b5',
      start: '2026-10-25T12:00',
      end: '2026-10-25T13:00',
      unscheduled: true,
    },
  ];

  myColors: MbscCalendarColor[] = [];
  contBg = '';

  calendarOptions: MbscEventcalendarOptions = {
    view: {
      scheduler: {
        type: 'day',
        startTime: '08:00',
        endTime: '20:00',
        allDay: false,
      },
    },
    resources: [
      {
        id: 1,
        name: 'Dr. Keila Delores',
      },
      {
        id: 2,
        name: 'Dr. Gene Cortez',
      },
      {
        id: 3,
        name: 'Dr. Paula Bush',
      },
      {
        id: 4,
        name: 'Dr. Pete Nichols',
      },
      {
        id: 5,
        name: 'Dr. Jean Pearson',
        color: '#8f1ed6',
      },
      {
        id: 6,
        name: 'Dr. Thelma Cain',
      },
    ],
    invalid: [
      {
        recurring: {
          repeat: 'daily',
          until: yesterday,
        },
      },
      {
        start: yesterday,
        end: today,
      },
    ],
    dragToMove: true,
    dragToCreate: true,
    eventOverlap: false,
    externalDrop: true,
    externalDrag: true,
    extendDefaultEvent: () => ({
      job: 'Tartar removal',
      color: '#a446b5',
    }),
    onEventCreate: (args) => {
      const event: MyEvent = args.event;
      event.unscheduled = false;
      this.myColors = [];
    },
    onEventCreated: (args) => {
      setTimeout(() => {
        this.notify.toast({
          message: args.event.title + ' added',
        });
        this.appointments = this.appointments.filter((item) => item.id !== args.event.id);
      });
    },
    onEventCreateFailed: (args) => {
      this.notify.toast({
        message: args.event.start! <= today ? "Can't add event in the past" : 'Make sure not to double book',
      });
    },
    onEventUpdateFailed: (args) => {
      this.notify.toast({
        message: args.event.start! <= today ? "Can't add event in the past" : 'Make sure not to double book',
      });
    },
    onEventDelete: (args) => {
      this.notify.toast({
        message: args.event.title + ' unscheduled',
      });
    },
    onEventDragEnter: () => {
      this.myColors = [
        {
          background: '#f1fff24d',
          start: '08:00',
          end: '20:00',
          recurring: {
            repeat: 'daily',
          },
        },
      ];
    },
    onEventDragLeave: () => {
      this.myColors = [];
    },
  };

  getHours(event: MbscCalendarEvent) {
    const eventLength = Math.abs(+new Date(event.end as string) - +new Date(event.start as string)) / (60 * 60 * 1000);
    return eventLength + ' hour' + (eventLength > 1 ? 's' : '');
  }

  onItemDrop(args: any): void {
    if (args.data) {
      const event: MyEvent = args.data;
      event.unscheduled = true;
      this.appointments = [...this.appointments, event];
    }
    this.contBg = '';
  }

  onItemDragEnter(args: any): void {
    const event: MyEvent = args.data;
    if (!(event && event.unscheduled)) {
      this.contBg = '#d0e7d2cc';
    }
  }

  onItemDragLeave(): void {
    this.contBg = '';
  }

  ngOnInit(): void {
    for (const event of this.myData) {
      // Convert dates to date objects
      event.start = event.start ? new Date(event.start as string) : event.start;
      event.end = event.end ? new Date(event.end as string) : event.end;
      // Mark past events as fixed by setting the event.editable property to false
      event.editable = !!(event.start && today < event.start);
    }
  }
}

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.