---
title: "Angular Calendar Appointment Booking Example | Mobiscroll"
component: "Calendar"
framework: "Angular"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/angular/calendar/appointment-booking"
---

# Appointment Booking - Angular Calendar demo

Build booking date selection interfaces with an inline Angular calendar — show per-day prices and disable fully booked dates for appointment flows.

- [Live demo](https://demo.mobiscroll.com/angular/calendar/appointment-booking)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-angular/tree/main/src/demos/datepicker/calendar/appointment-booking)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/calendar/appointment-booking.md), [jQuery](https://demo.mobiscroll.com/jquery/calendar/appointment-booking.md), [React](https://demo.mobiscroll.com/react/calendar/appointment-booking.md), [Vue](https://demo.mobiscroll.com/vue/calendar/appointment-booking.md)

## Description

Book single or multiple appointments depending on the use-case or set up recurring date and time selection. Limit the available dates with the `min` and `max` options. Make dates un-selectable with the `invalid` option. Load pricing, available spots on demand and show it as [labels](https://demo.mobiscroll.com/angular/calendar/dots-colors-labels) that help people book the appointment.

The calendar can be displayed inline with one or more months or linked to an input. [Learn how to initialize the calendar.](https://demo.mobiscroll.com/angular/calendar/usage-on-input-or-inline)

**Looking for flight or hotel booking?** [Check out the range picker →](https://demo.mobiscroll.com/angular/range/flight-booking)

## What this demo shows

- Three inline appointment-booking examples that show different date-selection patterns.
- **Single-date booking** The first example shows an inline calendar with two months visible side by side.
- **Single-date booking** Past dates are disabled, and booking is limited to a six-month window starting from today.
- **Single-date booking** Some dates are unavailable because they are already booked.
- **Single-date booking** Each day shows a price label below the date.
- **Date and time booking** The second example pairs a single-month date picker with a time slot picker shown next to it.
- **Date and time booking** Users can browse dates from today up to six months ahead and then select from the available time slots for the chosen day.
- **Date and time booking** Each day shows how many spots are still available.
- **Date and time booking** Fully booked days are disabled, and taken time slots remain visible but cannot be selected.
- **Multi-date booking** The third example shows an inline two-month calendar with multiple date selection enabled.
- **Multi-date booking** Each day shows the number of available spots, and dates with no remaining availability are disabled.
- **Calendar header** All three examples include month labels and navigation arrows in the calendar header.

## Best for

- **Single-appointment booking** Consumer-facing flows where users need to choose one available date and see day-based pricing.
- **Date and time selection** Booking experiences where users pick a date first and then choose from the remaining time slots for that day.
- **Multi-visit booking** Flows that let people book multiple appointment dates in one interaction.
- **Availability-driven booking** Interfaces that need to show remaining capacity, disable fully booked dates, and surface useful day-level booking details.
- **Common appointment scenarios** Service bookings, home visits, consultations, demos, sales meetings, and similar scheduled appointments.

## Tutorial

### Single date selection

For enhanced validation set `min` and `max` dates. In addition to that disable invalid days by passing them to the `invalid` option.

If you need to show additional information, use the `labels` option and pass prices or availability. Invalids and things like pricing can be set on-load or on-demand in the `onPageLoading` lifecycle event.

```html
<mbsc-datepicker
    display="inline"
    [controls]="['calendar']"
    [min]="min"
    [max]="max"
    [labels]="singleLabels"
    [invalid]="singleInvalid"
    (onPageLoading)="onPageLoading($event)">
</mbsc-datepicker>
```

```ts
min = '2026-10-25T00:00';
max = '2027-04-25T00:00';

single: Date | undefined;
singleLabels = [];
singleInvalid = [];

onPageLoading(event: any): void {
    this.getPrices(event.firstDay, (bookings: any) => {
        this.singleLabels = bookings.labels;
        this.singleInvalid = bookings.invalid;
    });
}

getPrices(date: any, callback: any): void {
    const invalid: any = [];
    const labels: any = [];

    this.http.jsonp('//trial.mobiscroll.com/getprices/?year=' + date.getFullYear() + '&month=' + date.getMonth(), 'callback').subscribe((bookings: any) => {
        for (var i = 0; i < bookings.length; ++i) {
            const booking = bookings[i];
            const d = new Date(booking.d);

            if (booking.nr > 0) {
                labels.push({
                    start: d,
                    title: booking.nr + ' SPOTS',
                    textColor: '#e1528f'
                });
            } else {
                invalid.push(d);
            }
        }
        callback({ labels, invalid });
    });
}
```

### Booking a date and time

Use a `calendar` in combination with a `time` picker or `timegrid`. Dynamically filter the time grid based on availability and add labels to the dates to inform users of free spots.

```html
<mbsc-datepicker
    display="inline"
    minTime="08:00"
    maxTime="19:59"
    [controls]="['calendar', 'timegrid']"
    [min]="2026-10-25T00:00"
    [max]="2027-04-25T00:00"
    [stepMinute]="60"
    [labels]="myLabels"
    [invalid]="myInvalid">
</mbsc-datepicker>
```

```ts
// example for today's labels and invalids
myLabels = [{
    start: '2026-10-25',
    textColor: '#e1528f',
    title: '3 SPOTS'
}];
myInvalid = [{
    start: '2026-10-25T08:00',
    end: '2026-10-25T13:00'
}, {
    start: '2026-10-25T15:00',
    end: '2026-10-25T17:00'
}, {
    start: '2026-10-25T19:00',
    end: '2026-10-25T20:00'
}];
```

### Booking multiple appointments

For multiple event booking set the `selectMultiple` option to `'true'`. `min`, `max`, `invalid` are essential for a great UX and `labels` can be used for showing extra information about available spots.

```html
<mbsc-datepicker
    display="inline"
    [controls]="['calendar']"
    [min]="min"
    [max]="max"
    [labels]="multipleLabels"
    [invalid]="multipleInvalid"
    [selectMultiple]="true"
    (onPageLoading)="onPageLoading($event)">
</mbsc-datepicker>
```

```ts
min = '2026-10-25T00:00';
max = '2027-04-25T00:00';

multiple = [
    '2026-10-11T00:00',
    '2026-10-16T00:00',
    '2026-10-17T00:00'
];
multipleLabels = [];
multipleInvalid = [];

onPageLoading(event: any): void {
    this.getBookings(event.firstDay, (bookings: any) => {
        this.multipleLabels = bookings.labels;
        this.multipleInvalid = bookings.invalid;
    });
}

getBookings(date: any, callback: any): void {
    const invalid: any = [];
    const labels: any = [];

    this.http.jsonp('//trial.mobiscroll.com/getbookings/?year=' + date.getFullYear() + '&month=' + date.getMonth(), 'callback').subscribe((bookings: any) => {
        for (const booking of bookings) {
            const d = new Date(booking.d);
            if (booking.nr > 0) {
                labels.push({
                    start: d,
                    title: booking.nr + ' SPOTS',
                    textColor: '#e1528f'
                });
            } else {
                invalid.push(d);
            }
        }
        callback({ labels, invalid });
    });
}
```

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/angular/datepicker/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/angular/datepicker/api#opt-themeVariant) - option
- [`display`](https://mobiscroll.com/docs/angular/datepicker/api#opt-display) - option
- [`controls`](https://mobiscroll.com/docs/angular/datepicker/api#opt-controls) - option
- [`min`](https://mobiscroll.com/docs/angular/datepicker/api#opt-min) - option
- [`max`](https://mobiscroll.com/docs/angular/datepicker/api#opt-max) - option
- [`pages`](https://mobiscroll.com/docs/angular/datepicker/api#opt-pages) - option
- [`onPageLoading`](https://mobiscroll.com/docs/angular/datepicker/api#event-onPageLoading) - event
- [`minTime`](https://mobiscroll.com/docs/angular/datepicker/api#opt-minTime) - option
- [`maxTime`](https://mobiscroll.com/docs/angular/datepicker/api#opt-maxTime) - option
- [`stepMinute`](https://mobiscroll.com/docs/angular/datepicker/api#opt-stepMinute) - option
- [`selectMultiple`](https://mobiscroll.com/docs/angular/datepicker/api#opt-selectMultiple) - option
- [`labels`](https://mobiscroll.com/docs/angular/datepicker/api#opt-labels) - option
- [`invalid`](https://mobiscroll.com/docs/angular/datepicker/api#opt-invalid) - option

## Code

### Component

```ts
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { MbscDatepickerOptions, MbscModule, setOptions } from '@mobiscroll/angular';

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

const minDate = '2026-10-25T00:00';
const maxDate = '2027-04-25T00:00';

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

  multiple = ['2026-10-11T00:00', '2026-10-16T00:00', '2026-10-17T00:00'];

  singleLabels = [];
  singleInvalid = [];
  datetimeLabels = [];
  datetimeInvalid = [];
  multipleLabels = [];
  multipleInvalid = [];

  singleSettings: MbscDatepickerOptions = {
    display: 'inline',
    controls: ['calendar'],
    min: minDate,
    max: maxDate,
    pages: 'auto',
    onPageLoading: (args) => {
      this.getPrices(args.firstDay, (bookings: any) => {
        this.singleLabels = bookings.labels;
        this.singleInvalid = bookings.invalid;
      });
    },
  };

  datetimeSettings: MbscDatepickerOptions = {
    display: 'inline',
    controls: ['calendar', 'timegrid'],
    min: minDate,
    max: maxDate,
    minTime: '08:00',
    maxTime: '19:59',
    stepMinute: 60,
    onPageLoading: (args) => {
      this.getDatetimes(args.firstDay, (bookings: any) => {
        this.datetimeLabels = bookings.labels;
        this.datetimeInvalid = bookings.invalid;
      });
    },
  };

  multipleSettings: MbscDatepickerOptions = {
    display: 'inline',
    controls: ['calendar'],
    min: minDate,
    max: maxDate,
    pages: 'auto',
    selectMultiple: true,
    onPageLoading: (args) => {
      this.getBookings(args.firstDay, (bookings: any) => {
        this.multipleLabels = bookings.labels;
        this.multipleInvalid = bookings.invalid;
      });
    },
  };

  getPrices(d: Date, callback: any): void {
    const myInvalid: any = [];
    const myLabels: any = [];
    const y = d.getFullYear();
    const m = d.getMonth();

    this.http.jsonp('https://trial.mobiscroll.com/getprices/?year=' + y + '&month=' + m, 'callback').subscribe((bookings: any) => {
      for (const booking of bookings) {
        const date = new Date(booking.d);

        if (booking.price > 0) {
          myLabels.push({
            start: date,
            title: '$' + booking.price,
            textColor: '#e1528f',
          });
        } else {
          myInvalid.push(date);
        }
      }
      callback({ labels: myLabels, invalid: myInvalid });
    });
  }

  getDatetimes(d: Date, callback: any): void {
    let myInvalid: any = [];
    const myLabels: any = [];
    const y = d.getFullYear();
    const m = d.getMonth();

    this.http.jsonp('https://trial.mobiscroll.com/getbookingtime/?year=' + y + '&month=' + m, 'callback').subscribe((bookings: any) => {
      for (const booking of bookings) {
        const date = new Date(booking.d);
        if (booking.nr > 0) {
          myLabels.push({
            start: date,
            title: booking.nr + ' SPOTS',
            textColor: '#e1528f',
          });
          myInvalid = [...myInvalid, ...booking.invalid];
        } else {
          myInvalid.push(date);
        }
      }
      callback({ labels: myLabels, invalid: myInvalid });
    });
  }

  getBookings(d: Date, callback: any): void {
    const myInvalid: any = [];
    const myLabels: any = [];
    const y = d.getFullYear();
    const m = d.getMonth();

    this.http.jsonp('https://trial.mobiscroll.com/getbookings/?year=' + y + '&month=' + m, 'callback').subscribe((bookings: any) => {
      for (const booking of bookings) {
        const date = new Date(booking.d);
        if (booking.nr > 0) {
          myLabels.push({
            start: date,
            title: booking.nr + ' SPOTS',
            textColor: '#e1528f',
          });
        } else {
          myInvalid.push(date);
        }
      }
      callback({ labels: myLabels, invalid: myInvalid });
    });
  }
}
```

### Template

```html
<mbsc-page>
  <div class="dms-calendar-booking">
    <div class="mbsc-form-group">
      <div class="mbsc-form-group-title">Single date & appointment booking</div>
      <mbsc-datepicker [options]="singleSettings" [labels]="singleLabels" [invalid]="singleInvalid"></mbsc-datepicker>
    </div>
    <div class="mbsc-form-group">
      <div class="mbsc-form-group-title">Select date & time</div>
      <mbsc-datepicker [options]="datetimeSettings" [labels]="datetimeLabels" [invalid]="datetimeInvalid"></mbsc-datepicker>
    </div>
    <div class="mbsc-form-group">
      <div class="mbsc-form-group-title">Booking multiple appointments</div>
      <mbsc-datepicker [options]="multipleSettings" [labels]="multipleLabels" [invalid]="multipleInvalid"></mbsc-datepicker>
    </div>
  </div>
</mbsc-page>
```

### CSS

```css
.md-calendar-booking .mbsc-calendar-text {
  text-align: center;
}

.md-calendar-booking .booking-datetime .mbsc-datepicker-tab-calendar {
  flex: 1 1 0;
  min-width: 300px;
}

.md-calendar-booking .mbsc-timegrid-item {
  margin-top: 1.5em;
  margin-bottom: 1.5em;
}

.md-calendar-booking .mbsc-timegrid-container {
  top: 30px;
}
```

## Implementation notes

- **Booking window** Use `min` and `max` to limit selection to the allowed booking range, such as from today through the next six months.
- **Unavailable dates** Use `invalid` to disable dates that are fully booked or otherwise unavailable.
- **Day labels** Use `labels` to display supporting day-level information such as pricing or remaining spots.
- **Dynamic data loading** Set `invalid` dates and `labels` on load or update them in the `onPageLoading` lifecycle event when availability and pricing need to be loaded for the visible date range.
- **Date and time booking** Combine the calendar with a timegrid for date-and-time selection, then filter the time slots based on availability for the selected day.
- **Multi-date booking** Set `selectMultiple` to `true` when the booking flow needs to allow selecting more than one date.

## 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

- Next: [Custom Day Cells](https://demo.mobiscroll.com/angular/calendar/activity-calendar.md) - Add custom activity indicators, availability markers, or daily metrics to each day cell in the inline Angular calendar date picker.
