Appointment Scheduling
Usage
theme: 'ios' You can turn auto theming on and let it decide based on the platform.
Usage
themeVariant: 'light' You can turn auto dark mode on based on system settings.
Usage
clickToCreate: true,
// click to create is on dragToCreate: true,
// drag to create is on dragToMove: true,
// reordering is on dragToResize: true,
// resizing is on eventDelete: true
// deleting is on Fine-tune drag & drop with these options, which are false by default.
Usage
locale: localeEn Selecting the locale loads the localized settings and the translated UI text.
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.
Demo
The live demo didn't load.
Try it on demo.mobiscroll.com
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);
}
}
}
<div class="mbsc-grid mbsc-no-padding">
<div class="mbsc-row">
<div class="mbsc-col-sm-9 docs-appointment-calendar">
<mbsc-eventcalendar [data]="myData" [colors]="myColors" [options]="calendarOptions"></mbsc-eventcalendar>
</div>
<mbsc-dropcontainer
class="mbsc-col-sm-3 docs-appointment-cont"
(onItemDrop)="onItemDrop($event)"
(onItemDragEnter)="onItemDragEnter($event)"
(onItemDragLeave)="onItemDragLeave()"
[ngStyle]="{ background: contBg }"
>
<div class="mbsc-form-group-title">Unscheduled appointments</div>
<div
mbsc-draggable
*ngFor="let task of appointments"
[dragData]="task"
class="docs-appointment-task"
[ngStyle]="{ background: task.color }"
>
<div>{{ task.title }} - {{ task.job }}</div>
<div>{{ getHours(task) }}</div>
</div>
</mbsc-dropcontainer>
</div>
</div>
.docs-appointment-calendar {
border-right: 1px solid #ccc;
}
.docs-appointment-calendar .mbsc-readonly-event {
opacity: 0.6;
}
.docs-appointment-cont {
height: 100%;
overflow: auto;
}
.docs-appointment-task {
color: #fff;
background: #999;
padding: 10px;
margin: 20px;
border-radius: 8px;
font-family:
-apple-system,
Segoe UI,
Roboto,
sans-serif;
}
.demo-doctors-appointment.demo-wrapper,
.demo-doctors-appointment .mbsc-grid,
.demo-doctors-appointment .mbsc-row,
.demo-doctors-appointment .docs-appointment-calendar {
height: 100%;
}