How to Make Hotel Booking Calendar Using DHTMLX Scheduler and Angular
Tags: javascript, angular, webdev, coding
Published: 2023-10-17 15:28:19

Welcome to this tutorial, where we bring together two powerful tools: the DHTMLX [Scheduler library](https://dhtmlx.com/docs/products/dhtmlxScheduler/) and the [Angular](https://angular.io/) framework, to create a comprehensive hotel room booking application.

In this post our goal is to create an application that will look exactly like this:

![angular-scheduler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eux1x5wilhlkqvc0xopv.png)

Our Angular hotel booking app will be able to display hotel rooms, room types, and room status, reservations made for particular dates and booking status. The app will also allow performing CRUD operations.

If you’re new to configuring DHTMLX Scheduler for room reservation purposes or integrating it into Angular applications, don’t worry. We’ve got you covered with dedicated tutorials for both:

* [Configuring DHTMLX Scheduler for Hotel Booking](https://dhtmlx.com/blog/how-to-make-hotel-booking-calendar/)

* [Integrating DHTMLX Scheduler into an Angular App](https://dhtmlx.com/blog/angular-dhtmlxscheduler-tutorial/)

We also guide you through the process of setting up a Node.js server. You can find a separate tutorial devoted to working with Scheduler and Node.js [in the documentation](https://docs.dhtmlx.com/scheduler/howtostart_nodejs.html).

You can find the full source code of the Angular Hotel Room Reservation demo [in our GitHub repository](https://github.com/DHTMLX/angular-scheduler-hotel-booking).

And [here](https://youtu.be/23m7xUYMOqs) is the video tutorial with all the steps, which you can follow to get the result faster.

Let’s dive in!

#Step 0 – Prerequisites

Before we begin, make sure you have [Node.js](https://nodejs.org/en) and [Angular CLI](https://angular.io/cli).

#Step 1 – Preparing App

To create an app skeleton, run the following command:
```
ng new room-reservation-angular
```

After the operation finishes, we can go to the app directory and run the application:

```
cd room-reservation-angular
ng serve
```

Now if we open *http: //127.0.0.1:4200* we should see the initial page. The `ng serve` command will watch the source file and, if necessary, will change and rebuild the app.


![Angular-Booking-App-Step-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn1sx5hgk3kx1u3om7oy.png)

#Step 2 – Create the Data Models
Let’s define the `Reservation`, `Room`, `RoomType`, `CleaningStatus`, and `BookingStatus` models. Run the following commands:

```
ng generate interface models/reservation model
ng generate interface models/room model
ng generate interface models/roomType model
ng generate interface models/cleaningStatus model
ng generate interface models/bookingStatus model
```

In the newly created `reservation.model.ts` file inside the `models` folder, we will add the following code:

```
export interface Reservation {
    id: number;
    start_date: string;
    end_date: string;
    text: string;
    room: string;
    booking_status: string;
    is_paid: string;
}
```

In the `room.model.ts`, `room-type.model.ts`, `cleaning-status.model.ts`, `booking-status.model.ts` files, add the next lines of code:

```
export interface Room {
    id: number;
    value: string;
    label: string;
    type: string;
    cleaning_status: string;
}


export interface RoomType {
    id: string;
    value: string;
    label: string;
}


export interface CleaningStatus {
    id: string;
    value: string;
    label: string;
    color: string;
}


export interface BookingStatus {
    id: string;
    value: string;
    label: string;
}
```

#Step 3 – Creating Scheduler Component

Download the latest [free 30-day trial of the PRO edition of DHTMLX Scheduler](https://dhtmlx.com/docs/products/dhtmlxScheduler/download.shtml). Extract the downloaded package to your local machine to the root folder of your project. You can read the article “[Adding PRO Edition into Project](https://docs.dhtmlx.com/scheduler/install_with_bower.html#addingproeditionintoproject)” for more details.
To be able to embed Scheduler into the app, you should get the DHTMLX Scheduler code. Run the following command:

```
npm install ./scheduler_6.0.5_trial
```

Create a new component. For this, run the following command:

```
ng generate component scheduler --skip-tests
```

The newly created `scheduler.component.html` file inside the `scheduler` folder will contain the [template for the scheduler](https://docs.dhtmlx.com/scheduler/scheduler_markup.html). Let’s add the next lines of code:

```
<div #scheduler_here class='dhx_cal_container' style='width:100%; height:100vh'>
    <div class='dhx_cal_navline'>
        <div style='font-size:16px;padding:4px 20px;'>
            Show rooms:
            <select id='room_filter' [(ngModel)]='selectedRoomType' (ngModelChange)='filterRoomsByType($event)'></select>
        </div>
        <div class='dhx_cal_prev_button'>&nbsp;</div>
        <div class='dhx_cal_next_button'>&nbsp;</div>
        <div class='dhx_cal_today_button'></div>
        <div class='dhx_cal_date'></div>
    </div>
    <div class='dhx_cal_header'></div>
    <div class='dhx_cal_data'></div>
</div>
```

We utilized the `ngModel` and `ngModelChange` directives to establish interaction between the `select` element and data within the component. Please, add the `FormsModule` module to the `app.module.ts file`.

```
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';


import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SchedulerComponent } from './scheduler/scheduler.component';


import { FormsModule } from '@angular/forms';


@NgModule({
    declarations: [
        AppComponent,
        SchedulerComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        FormsModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

We’ll declare scheduler styles in the separate file named `scheduler.component.css`. Styles can look in the following way:

```
@import '~dhtmlx-scheduler/codebase/dhtmlxscheduler_flat.css';
:host {
    display: block;
    position: relative;
    height: 100%;
    width: 100%;
}
html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
}
.dhx_cal_container #room_filter:focus {
    outline: 1px solid #52daff;
}
.timeline-cell-inner {
    height: 100%;
    width: 100%;
    table-layout: fixed;
}
.timeline-cell-inner td {
    border-left: 1px solid #cecece;
}
.dhx_section_time select {
    display: none;
}
.timeline_weekend {
    background-color: #FFF9C4;
}
.timeline_item_cell {
    width: 32%;
    height: 100% !important;
    font-size: 14px;
    text-align: center;
    line-height: 50px;
}
.cleaning_status {
    position: relative;
}
.timeline_item_separator {
    background-color: #CECECE;
    width: 1px;
    height: 100% !important;
}
.dhx_cal_event_line {
    background-color: #FFB74D !important;
}
.event_1 {
    background-color: #FFB74D !important;
}
.event_2 {
    background-color: #9CCC65 !important;
}
.event_3 {
    background-color: #40C4FF !important;
}
.event_4 {
    background-color: #BDBDBD !important;
}
.booking_status,
.booking_paid {
    position: absolute;
    right: 5px;
}
.booking_status {
    top: 2px;
}
.booking_paid {
    bottom: 2px;
}
.dhx_cal_event_line:hover .booking-option {
    background: none !important;
}
.dhx_cal_header .dhx_scale_bar {
    line-height: 26px;
    color: black;
}
.dhx_section_time select {
    display: none
}
.dhx_mini_calendar .dhx_year_week,
.dhx_mini_calendar .dhx_scale_bar {
    height: 30px !important;
}
.dhx_cal_light_wide .dhx_section_time {
    text-align: left;
}
.dhx_cal_light_wide .dhx_section_time > input:first-child {
    margin-left: 10px;
}
.dhx_cal_light_wide .dhx_section_time input {
    border: 1px solid #aeaeae;
    padding-left: 5px;
}
.dhx_cal_light_wide .dhx_readonly {
    padding: 3px;
}
.collection_label .timeline_item_cell {
    line-height: 60px;
}
.dhx_cal_radio label,
.dhx_cal_radio input {
    vertical-align: middle;
}
.dhx_cal_radio input {
    margin-left: 10px;
    margin-right: 2px;
}
.dhx_cal_radio input:first-child {
    margin-left: 5px;
}
.dhx_cal_radio {
    line-height: 19px;
}
.dhtmlXTooltip.tooltip {
    color: #4d4d4d;
    font-size: 15px;
    line-height: 140%;
}
```

To make the scheduler container occupy the entire space of the body, you need to add the following styles to the `styles.css` file located in the `src` folder:

```
body,
html {
   width: 100%;
   height: 100%;
   margin: unset;
}
```

To proceed, we need to import the required modules and add the necessary code lines to the `scheduler.component.ts` file:

```
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Scheduler } from 'dhtmlx-scheduler';
import { Reservation } from "../models/reservation.model";
import { Room } from "../models/room.model";


@Component({
    encapsulation: ViewEncapsulation.None,
    selector: 'scheduler',
    styleUrls: ['./scheduler.component.css'],
    templateUrl: './scheduler.component.html'
})


export class SchedulerComponent implements OnInit {
    @ViewChild('scheduler_here', {static: true}) schedulerContainer!: ElementRef;
    private scheduler: any;
    rooms: any[] = [];
    roomTypes: any[] = [];
    cleaningStatuses: any[] = [];
    bookingStatuses: any[] = [];
    selectedRoomType: string = '';


    public filterRoomsByType(value: string): void {
        const currentRooms = value === 'all' ? this.rooms.slice() : this.rooms.filter(room => room.type === value);
        this.scheduler.updateCollection('currentRooms', currentRooms);
    };


    ngOnInit() {
        const scheduler = Scheduler.getSchedulerInstance();
        this.scheduler = scheduler;
        this.selectedRoomType = 'all';


        scheduler.plugins({
            limit: true,
            collision: true,
            timeline: true,
            editors: true,
            minical: true,
            tooltip: true
        });


        scheduler.locale.labels['section_text'] = 'Name';
        scheduler.locale.labels['section_room'] = 'Room';
        scheduler.locale.labels['section_booking_status'] = 'Booking Status';
        scheduler.locale.labels['section_is_paid'] = 'Paid';
        scheduler.locale.labels.section_time = 'Time';
        scheduler.xy.scale_height = 30;


        scheduler.config.details_on_create = true;
        scheduler.config.details_on_dblclick = true;
        scheduler.config.prevent_cache = true;
        scheduler.config.show_loading = true;
        scheduler.config.date_format = '%Y-%m-%d %H:%i';


        this.rooms = scheduler.serverList('rooms');
        this.roomTypes = scheduler.serverList('roomTypes');
        this.cleaningStatuses = scheduler.serverList('cleaningStatuses');
        this.bookingStatuses = scheduler.serverList('bookingStatuses');


        scheduler.config.lightbox.sections = [
            { name: 'text',           map_to: 'text',           type: 'textarea', height: 24 },
            { name: 'room',           map_to: 'room',           type: 'select', options: scheduler.serverList("currentRooms") },
            { name: 'booking_status', map_to: 'booking_status', type: 'radio', options: scheduler.serverList('bookingStatuses') },
            { name: 'is_paid',        map_to: 'is_paid',        type: 'checkbox', checked_value: true, unchecked_value: false },
            { name: 'time',           map_to: 'time',           type: 'calendar_time' }
        ];


        scheduler.locale.labels['timeline_tab'] = 'Timeline';


        scheduler.createTimelineView({
            name: 'timeline',
            y_property: 'room',
            render: 'bar',
            x_unit: 'day',
            x_date: '%d',
            dy: 52,
            event_dy: 48,
            section_autoheight: false,
   
            y_unit: scheduler.serverList('currentRooms'),
            second_scale: {
                x_unit: 'month',
                x_date: '%F %Y'
            },
            columns: [
                { label: 'Room', width: 70, template: (room: Room) => room.label },
                { label: 'Type', width: 90, template: (room: Room) => this.getRoomType(room.type) },
                { label: 'Status', width: 90, template: this.generateCleaningStatusColumnTemplate.bind(this) }
            ]
        });


        this.schedulerContainer.nativeElement.addEventListener('input', (event: any) => {
            const target =  event.target as HTMLSelectElement;


            if (target instanceof HTMLElement && target.closest('.cleaning-status-select')) {
                this.handleCleaningStatusChange(target);
            }
        });


        scheduler.date['timeline_start'] = scheduler.date.month_start;
        scheduler.date['add_timeline'] = (date: any, step: any) => scheduler.date.add(date, step, 'month');


        scheduler.attachEvent('onBeforeViewChange', (old_mode, old_date, mode, date) => {
            const year = date.getFullYear();
            const month = (date.getMonth() + 1);
            const d = new Date(year, month, 0);
            const daysInMonth = d.getDate();
            scheduler.matrix['timeline'].x_size = daysInMonth;


            return true;
        }, {});
   
        scheduler.templates.event_class = (start, end, event) => 'event_' + (event.booking_status || '');
   
        function getPaidStatus(isPaid: any) {
            return isPaid ? 'paid' : 'not paid';
        }


        const eventDateFormat = scheduler.date.date_to_str('%d %M %Y');
       
        scheduler.templates.event_bar_text = (start, end, event) => {
            const paidStatus = getPaidStatus(event.is_paid);
            const startDate = eventDateFormat(event.start_date);
            const endDate = eventDateFormat(event.end_date);


            return [event.text + '<br />',
                startDate + ' - ' + endDate,
                `<div class='booking_status booking-option'>${this.getBookingStatus(event.booking_status)}</div>`,
                `<div class='booking_paid booking-option'>${paidStatus}</div>`].join('');
        };
   
        scheduler.templates.tooltip_text = (start, end, event) => {
            const room = this.getRoom(event.room) || {label: ''};
   
            const html = [];
            html.push('Booking: <b>' + event.text + '</b>');
            html.push('Room: <b>' + room.label + '</b>');
            html.push('Check-in: <b>' + eventDateFormat(start) + '</b>');
            html.push('Check-out: <b>' + eventDateFormat(end) + '</b>');
            html.push(this.getBookingStatus(event.booking_status) + ', ' + getPaidStatus(event.is_paid));


            return html.join('<br>')
        };


        scheduler.templates.lightbox_header = (start, end, ev) => {
            const formatFunc = scheduler.date.date_to_str('%d.%m.%Y');


            return formatFunc(start) + ' - ' + formatFunc(end);
        };
   
        scheduler.attachEvent('onEventCollision', (ev, evs) => {
            for (let i = 0; i < evs.length; i++) {
                if (ev.room != evs[i].room) {
                    continue;
                }
               
                scheduler.message({
                    type: 'error',
                    text: 'This room is already booked for this date.'
                });
            }


            return true;
        }, {});
   
        scheduler.attachEvent('onEventCreated', (event_id) => {
            const ev = scheduler.getEvent(event_id);
            ev.booking_status = 1;
            ev.is_paid = false;
            ev.text = 'new booking';
        }, {});
   
        scheduler.addMarkedTimespan({days: [0, 6], zones: 'fullday', css: 'timeline_weekend'});


        function setHourToNoon(event: any) {
            event.start_date.setHours(12, 0, 0);
            event.end_date.setHours(12, 0, 0);
        }


        scheduler.attachEvent('onEventLoading', (ev) => {
            this.filterRoomsByType('all');
            const select = document.getElementById('room_filter') as HTMLSelectElement;


            if (select !== null) {
                const selectHTML = [`<option value='all'>All</option>`];


                for (let i = 1; i < this.roomTypes.length + 1; i++) {
                    const roomType = this.roomTypes[i-1];
                    selectHTML.push(`<option value='${roomType.key}'>${this.getRoomType(roomType.key)}</option>`);
                }


                select.innerHTML = selectHTML.join('');
            }


            setHourToNoon(ev);


            return true;
        }, {});
       
        scheduler.attachEvent('onEventSave', (id, ev, is_new) => {
            if (!ev.text) {
                scheduler.alert('Text must not be empty');


                return false;
            }


            setHourToNoon(ev);


            return true;
        }, {});


        scheduler.attachEvent('onEventChanged', (id, ev) => {
            setHourToNoon(ev);
        }, {});


        scheduler.init(this.schedulerContainer.nativeElement, new Date(), 'timeline');
    }


    ngOnDestroy() {
        const scheduler = this.scheduler;
        scheduler && scheduler.destructor();
    }
   
    getRoom(key: any) {
        return this.rooms.find(room => room.key === key) || null;
    }


    getRoomType(key: any) {
        const roomType = this.roomTypes.find(item => item.key === key);


        return roomType ? roomType.label : null;
    }


    getCleaningStatus(key: any) {
        const cleaningStatus = this.cleaningStatuses.find(item => item.key === key);


        return cleaningStatus ? cleaningStatus.label : null;
    }


    getCleaningStatusIndicator(key: any) {
        const cleaningStatus = this.cleaningStatuses.find(item => item.key === key);


        return cleaningStatus ? cleaningStatus.color : null;
    }
   
    getBookingStatus(key: any) {
        const bookingStatus = this.bookingStatuses.find(item => item.key === key);


        return bookingStatus ? bookingStatus.label : '';
    }


    handleCleaningStatusChange(target: HTMLSelectElement) {
        const roomId = target.getAttribute('room-id');
        const selectedCleaningStatus = target.value;
        const roomToUpdate = this.rooms.find(room => room.id == roomId);


        if (roomToUpdate) {
            roomToUpdate.cleaning_status = selectedCleaningStatus;
        }
   
        const backgroundColor = this.getCleaningStatusIndicator(selectedCleaningStatus);
        target.style.backgroundColor = backgroundColor;
    }


    generateCleaningStatusColumnTemplate(room: Room) {
        const backgroundColor = this.getCleaningStatusIndicator(room.cleaning_status);
        const rgbaBackgroundColor = this.hexToRgba(backgroundColor, 0.2);
   
        const selectHTML = [`
            <select class='cleaning-status-select'
                room-id='${room.id}'
                style='width: 100%; height: 52px; background-color: ${rgbaBackgroundColor}; outline: none; border: none;'>
        `];
   
        this.cleaningStatuses.forEach(status => {
            const optionHTML = `
                <option value='${status.key}' style='background-color: ${status.color};' ${room.cleaning_status === status.key ? 'selected' : ''}>
                ${this.getCleaningStatus(status.key)}
                </option>
            `;
            selectHTML.push(optionHTML);
        });
   
        selectHTML.push(`</select>`);
   
        return selectHTML.join('');
    }


    hexToRgba(hex: any, alpha: any) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);


        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }
}
```

Feel free to check out the complete code of `scheduler.component.ts` [on GitHub](https://github.com/DHTMLX/angular-scheduler-hotel-booking/blob/main/src/app/scheduler/scheduler.component.ts).

Now let’s add the new component to the page. For this purpose, open `app.component.html` (located in `src/app`) and insert the scheduler tag in there:

```
<scheduler></scheduler>
```

At the next step, we will proceed to load and save data.

#Step 4 – Providing and Saving Data

##Data Loading

To add data loading to the Angular Scheduler, you need to add the **reservation** and **collections** services. But before that, let’s create and configure an environment file for a project. Run the following command:

```
ng generate environments
```

Let’s also create a helper for errors that will notify users by sending error messages to the console when something goes wrong. To do so, create the `service-helper.ts` file inside the `app/services` folder with the following code:

```
export function HandleError(error: any): Promise<any>{
    console.log(error);
    return Promise.reject(error);
}
```

Now let’s create the **reservations** and **collections** services. Run the following commands:

```
ng generate service services/reservation --flat --skip-tests
ng generate service services/collections --flat --skip-tests
```

In the newly created `reservation.service.ts` file inside the `services` folder, we will add the following code:

```
import { Injectable } from '@angular/core';
import { Reservation } from "../models/reservation";
import { HttpClient } from "@angular/common/http";
import { HandleError } from "./service-helper";
import { firstValueFrom } from 'rxjs';
import { environment } from '../../environments/environment.development';


@Injectable()
export class ReservationService {
    private reservationUrl = `${environment.apiBaseUrl}/reservations`;


    constructor(private http: HttpClient) { }


    get(): Promise<Reservation[]>{
        return firstValueFrom(this.http.get(this.reservationUrl))
            .catch(HandleError);
    }


    insert(reservation: Reservation): Promise<Reservation> {
        return firstValueFrom(this.http.post(this.reservationUrl, reservation))
            .catch(HandleError);
    }


    update(reservation: Reservation): Promise<void> {
        return firstValueFrom(this.http.put(`${this.reservationUrl}/${reservation.id}`, reservation))
            .catch(HandleError);
    }


    remove(id: number): Promise<void> {
        return firstValueFrom(this.http.delete(`${this.reservationUrl}/${id}`))
            .catch(HandleError);
    }
}
```

In the newly created `collections.service.ts` file, add the next lines of code:

```
import { Injectable } from '@angular/core';
import { Room } from "../models/room.model";
import { RoomType } from "../models/room-type.model";
import { CleaningStatus } from "../models/cleaning-status.model";
import { BookingStatus } from "../models/booking-status.model";
import { HttpClient } from "@angular/common/http";
import { HandleError } from "./service-helper";
import { firstValueFrom } from 'rxjs';
import { environment } from '../../environments/environment.development';


@Injectable()
export class CollectionsService {
    private collectionsUrl = `${environment.apiBaseUrl}/collections`;


    constructor(private http: HttpClient) { }


    getRooms(): Promise<Room[]>{
        return firstValueFrom(this.http.get(`${this.collectionsUrl}/rooms`))
            .catch(HandleError);
    }


    updateRoom(room: Room): Promise<void> {
        return firstValueFrom(this.http.put(`${this.collectionsUrl}/rooms/${room.id}`, room))
            .catch(HandleError);
    }


    getRoomTypes(): Promise<RoomType[]>{
        return firstValueFrom(this.http.get(`${this.collectionsUrl}/roomTypes`))
            .catch(HandleError);
    }


    getCleaningStatuses(): Promise<CleaningStatus[]>{
        return firstValueFrom(this.http.get(`${this.collectionsUrl}/cleaningStatuses`))
            .catch(HandleError);
    }


    getBookingStatuses(): Promise<BookingStatus[]>{
        return firstValueFrom(this.http.get(`${this.collectionsUrl}/bookingStatuses`))
            .catch(HandleError);
    }
}
```

The `get()`, `getRooms()`, `getRoomTypes()`, `getCleaningStatuses()` and `getBookingStatuses()` methods retrieve data from the server.

The `reservationUrl` and `collectionsUrl` are private elements of the services. They contain the URL to the REST API. In order to send HTTP requests, an HTTP class has been injected into the service.

To insert a new item, you need to send a POST request to the URL with the new item in its body.

To update an item, you need to send a PUT request to the `url/item_id`. This request also contains the updated item in its body. To remove an item, you need to send a delete request to the `url/item_id.`

##CRUD Operations

The services should handle CRUD operations in the scheduler. HTTP communication has been enabled by adding the `HttpClient` module in the `reservations.service.ts` and `collections.service.ts` files:

```
import { HttpClient } from "@angular/common/http";
```

This step allows fetching data seamlessly within our Angular application.

To utilize the `HttpClient` module, it is also required to include the essential HttpClientModule from the `@angular/common/http` package. In the `app.module.ts` file, you should update the imports array as follows:

```
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';


import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SchedulerComponent } from './scheduler/scheduler.component';


import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';


@NgModule({
    declarations: [
        AppComponent,
        SchedulerComponent
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        FormsModule,
        HttpClientModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }
```

The scheduler component is supposed to use the `ReservationService` and `CollectionsService` to get/insert/update/remove reservations and collections. To enable these options, let’s add the `ReservationService` and `CollectionsService` to the component. First, import the necessary modules for the service in `scheduler.component.ts`:

```
import { ReservationService } from '../services/reservation.service';
import { CollectionsService } from '../services/collections.service';
```

You should also specify the `EventService` as a provider in the `@Component` decorator:

```
providers: [ ReservationService, CollectionsService ]
```

Now every time a new `SchedulerComponent` is initialized, a fresh instance of the service will be created.

The services should be prepared to be injected into the component. For this purpose, add the following constructor to the `SchedulerComponent` class:

```
constructor(
    private reservationService: ReservationService,
    private collectionsService: CollectionsService
) { }
```

Next, we’ll add the `updateRoom()` method to save the room cleaning status changes in the database:

```
handleCleaningStatusChange(target: HTMLSelectElement) {
    ...
    this.collectionsService.updateRoom(roomToUpdate);
}
```

You need to modify the `ngOnInit` function to call the services to get the function and then wait for a response to put the data to the scheduler.

```
scheduler.init(this.schedulerContainer.nativeElement, new Date(), 'timeline');


const dp = scheduler.createDataProcessor({
    event: {
        create: (data: Reservation) => this.reservationService.insert(data),
        update: (data: Reservation) => this.reservationService.update(data),
        delete: (id: number) => this.reservationService.remove(id),
    }
});


forkJoin({
    reservations: this.reservationService.get(),
    rooms: this.collectionsService.getRooms(),
    roomTypes: this.collectionsService.getRoomTypes(),
    cleaningStatuses: this.collectionsService.getCleaningStatuses(),
    bookingStatuses: this.collectionsService.getBookingStatuses()
}).subscribe({
    next: ({ reservations, rooms, roomTypes, cleaningStatuses, bookingStatuses }) => {
        const data = {
            events: reservations,
            collections: {
                rooms,
                roomTypes,
                cleaningStatuses,
                bookingStatuses,
            }
        };


        scheduler.parse(data);
    },
    error: error => {
        console.error('An error occurred:', error);
    }
});
```

The `scheduler.parse` accepts a [data object in the JSON format](https://docs.dhtmlx.com/scheduler/data_formats.html#json). To efficiently wait for the completion of multiple asynchronous requests and load their data (reservations and collections) into the scheduler, you can utilize the `forkJoin` operator from the RxJS library. Please include the import:

```
import { forkJoin } from 'rxjs';
```

You can check the complete code for the `scheduler.components.ts` file [on GitHub](https://github.com/DHTMLX/angular-scheduler-hotel-booking/blob/main/src/app/scheduler/scheduler.component.ts).

#Step 5 – Server Configuration

Now, let’s move on to setting up the Node.js server for our app.
This tutorial uses the [Express](http://expressjs.com/) framework and MySQL as a data storage.

##Adding dependencies and installing modules

You should set up your MySQL server, or you can use another service, e.g. [Free MySQL Hosting](https://www.freemysqlhosting.net/).

Add express, mysql, and date-format-lite modules:

```
$ npm install express mysql date-format-lite
```

The `server.js` has been specified as the entry point above. Now let’s create the server folder in the root of the project and add the `server.js` file with the code below:

```
const express = require('express'); // use Express
const app = express(); // create application
const port = 3000; // port for listening
const cors = require('cors');
app.use(cors()); // enable CORS for all routes


// MySQL will be used for db access and util to promisify queries
const util = require('util');
const mysql = require('mysql');


// use your own parameters for database
const mysqlConfig = {
    'connectionLimit': 10,
    'host': 'localhost',
    'user': 'root',
    'password': '',
    'database': 'room_reservation_node'
};


app.use(express.json()); // Enable JSON body parsing
// return static pages from the './public' directory
app.use(express.static(__dirname + '/public'));


// start server
app.listen(port, () => {
    console.log('Server is running on port ' + port + '...');
});


const router = require('./router');


// open connection to mysql
const connectionPool = mysql.createPool(mysqlConfig);
connectionPool.query = util.promisify(connectionPool.query);


// add listeners to basic CRUD requests
const DatabaseHandler = require('./databaseHandler');
const databaseHandler = new DatabaseHandler(connectionPool);
router.setRoutes(app, '/data', databaseHandler);
```

Then open the `package.json` file and replace the `start` statement with:

```
"scripts": {
    "ng": "ng",
    "start": "concurrently \"node server/server.js\" \"ng serve\"",
    …
```

We’ll utilize the `concurrently` package to enable simultaneous launching of both the server and the client application. So, add the `concurrently` module:

```
$ npm install concurrently
```

##Preparing a database

Let’s connect Scheduler to the database and define methods to read and write items in it.

* Creating a database:

First things first, we need a database to work with. You can create a database with your favorite mysql-client or via the console. To create a database with a mysql-client, open it and execute the code below. For creating `reservations` tables:

```
CREATE TABLE `reservations` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`start_date` datetime NOT NULL,
`end_date` datetime NOT NULL,
`text` varchar(255) DEFAULT NULL,
`room` varchar(255) DEFAULT NULL,
`booking_status` varchar(255) DEFAULT NULL,
`is_paid` BOOLEAN DEFAULT NULL CHECK (is_paid IN (0, 1)),
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
```

Let’s add some test data:

```
INSERT INTO `reservations` VALUES (2, '2023-08-01', '2023-08-11', 'RSV2023-08-01ABC124', 3, 4, true);
INSERT INTO `reservations` VALUES (3, '2023-08-07', '2023-08-17', 'RSV2023-08-07ABC126', 5, 3, true);
INSERT INTO `reservations` VALUES (4, '2023-08-04', '2023-08-16', 'RSV2023-08-04ABC125', 7, 4, false);
INSERT INTO `reservations` VALUES (13, '2023-07-28', '2023-08-14', 'RSV2023-07-28ABC123', 1, 4, true);
INSERT INTO `reservations` VALUES (14, '2023-08-14', '2023-08-27', 'RSV2023-08-14ABC129', 1, 3, false);
INSERT INTO `reservations` VALUES (15, '2023-08-19', '2023-08-29', 'new booking', 4, 1, false);
INSERT INTO `reservations` VALUES (16, '2023-08-24', '2023-08-31', 'new booking', 11, 1, false);
INSERT INTO `reservations` VALUES (17, '2023-08-17', '2023-08-26', 'RSV2023-08-17ABC135', 6, 2, false);
INSERT INTO `reservations` VALUES (18, '2023-08-18', '2023-08-31', 'RSV2023-08-18ABC139', 9, 2, false);
INSERT INTO `reservations` VALUES (19, '2023-08-02', '2023-08-12', 'RSV2023-08-02ABC127', 10, 4, true);
INSERT INTO `reservations` VALUES (20, '2023-08-12', '2023-08-21', 'RSV2023-08-12ABC130', 10, 3, false);
```

For creating `rooms` tables:

```
CREATE TABLE `rooms` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`cleaning_status` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
```

Let’s add some test data:

```
INSERT INTO `rooms` VALUES ('1', '1', '101', '1', '1');
INSERT INTO `rooms` VALUES ('2', '2', '102', '1', '3');
INSERT INTO `rooms` VALUES ('3', '3', '103', '1', '2');
INSERT INTO `rooms` VALUES ('4', '4', '104', '1', '1');
INSERT INTO `rooms` VALUES ('5', '5', '105', '2', '1');
INSERT INTO `rooms` VALUES ('6', '6', '201', '2', '2');
INSERT INTO `rooms` VALUES ('7', '7', '202', '2', '1');
INSERT INTO `rooms` VALUES ('8', '8', '203', '3', '3');
INSERT INTO `rooms` VALUES ('9', '9', '204', '3', '3');
INSERT INTO `rooms` VALUES ('10', '10', '301', '4', '2');
INSERT INTO `rooms` VALUES ('11', '11', '302', '4', '2');
INSERT INTO `rooms` VALUES ('12', '12', '303', '1', '2');
```

For creating the `roomTypes` tables:

```
CREATE TABLE `roomTypes` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
```

Let’s add some test data:

```
INSERT INTO `roomTypes` VALUES ('1', '1', '1 bed');
INSERT INTO `roomTypes` VALUES ('2', '2', '2 bed');
INSERT INTO `roomTypes` VALUES ('3', '3', '3 bed');
INSERT INTO `roomTypes` VALUES ('4', '4', '4 bed');
```

For creating `cleaningStatuses` tables:

```
CREATE TABLE `cleaningStatuses` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
`color` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
```

Let’s add some test data:

```
INSERT INTO `cleaningStatuses` VALUES ('1', '1', 'Ready', '#43a047');
INSERT INTO `cleaningStatuses` VALUES ('2', '2', 'Dirty', '#e53935');
INSERT INTO `cleaningStatuses` VALUES ('3', '3', 'Clean up', '#ffb300');
```

For creating `bookingStatuses` tables:

```
CREATE TABLE `bookingStatuses` (
`id` bigint(20) unsigned AUTO_INCREMENT,
`value` varchar(255) DEFAULT NULL,
`label` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
```

Let’s add some test data:

```
INSERT INTO `bookingStatuses` VALUES ('1', '1', 'New');
INSERT INTO `bookingStatuses` VALUES ('2', '2', 'Confirmed');
INSERT INTO `bookingStatuses` VALUES ('3', '3', 'Arrived');
INSERT INTO `bookingStatuses` VALUES ('4', '4', 'Checked Out');
```

* Implementing data access:

All the read/write logic will be defined in a separate module called `DatabaseHandler`. It’ll take a mysql connection and perform simple CRUD operations in the specified table: read all the items, insert new items, update or delete the existing ones. For this, create the `databaseHandler.js` file and add the code below into it:

```
require('date-format-lite'); // add date format


class DatabaseHandler {
    constructor(connection, table) {
        this._db = connection;
        this.table = 'reservations';
    }


    /// ↓↓↓ reservations handler ↓↓↓
    // get reservations, use dynamic loading if parameters sent
    async getAllReservations(params) {
        let query = 'SELECT * FROM ??';
        let queryParams = [
            this.table
        ];
       
        let result = await this._db.query(query, queryParams);


        result.forEach((entry) => {
            // format date and time
            entry.start_date = entry.start_date.format('YYYY-MM-DD hh:mm');
            entry.end_date = entry.end_date.format('YYYY-MM-DD hh:mm');
        });


        return result;
    }


    // create new reservation
    async insert(data) {
        let result = await this._db.query(
            'INSERT INTO ?? (`start_date`, `end_date`, `text`, `room`, `booking_status`, `is_paid`) VALUES (?,?,?,?,?,?)',
            [this.table, data.start_date, data.end_date, data.text, data.room, data.booking_status, data.is_paid]);


        return {
            action: 'inserted',
            tid: result.insertId
        }
    }


    // update reservation
    async update(id, data) {
        await this._db.query(
            'UPDATE ?? SET `start_date` = ?, `end_date` = ?, `text` = ?, `room` = ?, `booking_status` = ?, `is_paid` = ? WHERE id = ?',
            [this.table, data.start_date, data.end_date, data.text, data.room, data.booking_status, data.is_paid, id]);


        return {
            action: 'updated'
        }
    }


    // delete reservation
    async delete(id) {
        await this._db.query(
            'DELETE FROM ?? WHERE `id`=? ;',
            [this.table, id]);


        return {
            action: 'deleted'
        }
    }
    /// ↑↑↑ reservations handler ↑↑↑


    /// ↓↓↓ room cleanup status handler ↓↓↓
    // get rooms
    async getAllRooms(params) {
        let query = 'SELECT * FROM ??';
        let queryParams = [
            'rooms'
        ];
       
        let result = await this._db.query(query, queryParams);


        return result;
    }


    // update room cleanup status
    async updateRoomCleaningStatus(id, data) {
        await this._db.query(
            'UPDATE ?? SET `value` = ?, `label` = ?, `type` = ?, `cleaning_status` = ? WHERE id = ?',
            ['rooms', data.key, data.label, data.type, data.cleaning_status, id]);


        return {
            action: 'updated'
        }
    }
    /// ↑↑↑ room cleanup status handler ↑↑↑


    /// ↓↓↓ get room types ↓↓↓
    async getRoomTypes(params) {
        let query = 'SELECT * FROM ??';
        let queryParams = [
            'roomTypes'
        ];
       
        let result = await this._db.query(query, queryParams);


        return result;
    }
    /// ↑↑↑ get room types ↑↑↑


    /// ↓↓↓ get cleaning statuses ↓↓↓
    async getCleaningStatuses(params) {
        let query = 'SELECT * FROM ??';
        let queryParams = [
            'cleaningStatuses'
        ];
       
        let result = await this._db.query(query, queryParams);


        return result;
    }
    /// ↑↑↑ get cleaning statuses ↑↑↑


    /// ↓↓↓ get booking statuses ↓↓↓
    async getBookingStatuses(params) {
        let query = 'SELECT * FROM ??';
        let queryParams = [
            'bookingStatuses'
        ];
       
        let result = await this._db.query(query, queryParams);


        return result;
    }
    /// ↑↑↑ get booking statuses ↑↑↑
}


module.exports = DatabaseHandler;
```

###Routing

Then you need to set up routes, so that the storage could be accessed by the scheduler you have placed on the page.
For this, create another helper module and call it `router.js`:

```
function callMethod (method) {
    return async (req, res) => {
        let result;


        try {
            result = await method(req, res);
        } catch (e) {
            result =  {
                action: 'error',
                message: e.message
            }
        }


        res.send(result);
    }
};


module.exports = {
    setRoutes (app, prefix, databaseHandler) {
        /// ↓↓↓ reservations router ↓↓↓
        app.get(`${prefix}/reservations`, callMethod((req) => {
            return databaseHandler.getAllReservations(req.query);
        }));


        app.post(`${prefix}/reservations`, callMethod((req) => {
            return databaseHandler.insert(req.body);
        }));


        app.put(`${prefix}/reservations/:id`, callMethod((req) => {
            return databaseHandler.update(req.params.id, req.body);
        }));


        app.delete(`${prefix}/reservations/:id`, callMethod((req) => {
            return databaseHandler.delete(req.params.id);
        }));
        /// ↑↑↑ reservations router ↑↑↑
       
        /// ↓↓↓ rooms router ↓↓↓
        app.get(`${prefix}/collections/rooms`, callMethod((req) => {
            return databaseHandler.getAllRooms(req.query);
        }));


        app.put(`${prefix}/collections/rooms/:id`, callMethod((req) => {
            return databaseHandler.updateRoomCleaningStatus(req.params.id, req.body);
        }));
        /// ↑↑↑ rooms router ↑↑↑


        /// ↓↓↓ room types router ↓↓↓
        app.get(`${prefix}/collections/roomTypes`, callMethod((req) => {
            return databaseHandler.getRoomTypes(req.query);
        }));
        /// ↑↑↑ room types router ↑↑↑


        /// ↓↓↓ cleaning statuses router ↓↓↓
        app.get(`${prefix}/collections/cleaningStatuses`, callMethod((req) => {
            return databaseHandler.getCleaningStatuses(req.query);
        }));
        /// ↑↑↑ cleaning statuses router ↑↑↑


        /// ↓↓↓ booking statuses router ↓↓↓
        app.get(`${prefix}/collections/bookingStatuses`, callMethod((req) => {
            return databaseHandler.getBookingStatuses(req.query);
        }));
        /// ↑↑↑ booking statuses router ↑↑↑
    }
};
```

All it does is sets up the application to listen to request URLs that scheduler can send and calls the appropriate methods of the storage. Note that all methods are wrapped into try-catch blocks for you to be able to capture any error and return an appropriate error response to the client. Learn more about [error handling](https://docs.dhtmlx.com/scheduler/server_integration.html?_gl=1*1r5gjpa*_ga*MTMyODU1NTQxOC4xNjg1OTU4NTA2*_ga_N87XPB4GSG*MTY5NzUzNjkzNS45OC4xLjE2OTc1NTUxNDAuNjAuMC4w#errorhandling).

Also note that the exception message is written directly to the API response. It’s pretty handy during the development, but in the production environment it can be a good idea to hide these messages from the client side, since raw mysql exceptions that get there may contain sensitive data.

Now if you open the app page, you should see a scheduler with reservations. You can create, delete, and modify items within the scheduler. Any changes you make will be retained even if you reload the page.

![angular-js-scheduler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tiw5h7ju69ytvk2y86y.png)

#Conclusions

The Hotel Booking Calendar with Angular is ready! You are welcome to check out the [full source code on GitHub](https://github.com/DHTMLX/angular-scheduler-hotel-booking). The app can use RESTful API for CRUD operations.

We hope this guide has given you a clear understanding of how to integrate the room booking app based on DHTMLX Scheduler with Angular and connect it to a real backend. We’ll be glad if it helps you achieve your application development goals.

If you have any thoughts and ideas about the next topics/tutorials you would like to discover, don’t hesitate to share them in the comments.

*The article is originally published on the [DHTMLX blog](https://dhtmlx.com/blog/hotel-booking-calendar-dhtmlx-scheduler-angular/).*