
Syncfusion Angular Scheduler
- 205 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Use syncfusion-angular-scheduler for development tasks
About
syncfusion-angular-scheduler: A skill for development. This provides functionality for development workflows.
- syncfusion-angular-scheduler
Syncfusion Angular Scheduler by the numbers
- 205 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,904 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-schedulerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 205 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Use syncfusion-angular-scheduler for development tasks
Files
Implementing Syncfusion Angular Scheduler
A comprehensive guide for implementing the Syncfusion Angular Scheduler component for event and appointment management with multiple view modes, resource grouping, timezone support, and extensive customization options.
When to Use This Skill
Use this skill when you need to:
- Event and Appointment Management: Create calendar applications with event scheduling, editing, and management
- Resource Scheduling: Manage multiple resources (rooms, equipment, people) with grouping and filtering
- Timeline Views: Display events in horizontal timeline format for project planning or resource allocation
- Booking Systems: Build appointment booking interfaces for healthcare, salon, or meeting room management
- Calendar Applications: Implement full-featured calendar systems with recurring events and reminders
- Multi-Timezone Support: Handle events across different timezones for global teams
- Work Hour Management: Configure working days, hours, and holidays for business applications
Component Overview
The Syncfusion Angular Scheduler (ejs-schedule) is a feature-rich calendar and event management component that provides:
- 11+ View Modes: Day, Week, WorkWeek, Month, Year, Agenda, MonthAgenda, Timeline variants
- Event Types: Normal, spanned, all-day, and recurring events with complex recurrence patterns
- Data Binding: Local and remote data with CRUD operations, DataManager integration
- Resource Management: Single and multiple resource grouping with custom templates
- Time Configuration: Customizable timescale, timezone support, working hours/days
- Rich Customization: Cell templates, editor templates, event templates, custom styling
- Advanced Features: Drag-drop, resize, clipboard operations, virtual scrolling, export (ICS/Excel/PDF)
- Accessibility: WCAG 2.1 compliant with keyboard navigation and screen reader support
- Localization: Multi-language and RTL support
Package: @syncfusion/ej2-angular-schedule
Documentation and Navigation Guide
Getting Started
📄 Read: references/getting-started.md
Read this reference when you need to:
- Install and set up the Angular Scheduler package
- Configure module registration and view services (Day, Week, Month, Agenda, Timeline)
- Create your first scheduler with basic configuration
- Add CSS theme imports
- Understand component initialization
Views and Display Configuration
📄 Read: references/views-and-display.md
Read this reference when you need to:
- Configure available view modes (Day, Week, WorkWeek, Month, Year, Agenda)
- Set up Timeline views (TimelineDay, TimelineWeek, TimelineMonth, TimelineYear)
- Switch between different views programmatically
- Customize view-specific settings and intervals
- Configure calendar mode and display options
- Handle view navigation
Events and Appointments
📄 Read: references/appointments.md
Read this reference when you need to:
- Understand event types (normal, spanned, all-day, recurring)
- Define event data structure and required fields
- Create and render events programmatically
- Configure event templates and custom rendering
- Handle event interactions (click, double-click, hover)
- Add custom fields to events
Data Binding and CRUD Operations
📄 Read: references/data-binding.md
Read this reference when you need to:
- Bind local data (arrays) to the scheduler
- Connect to remote data sources (OData, Web API, REST services)
- Use DataManager for advanced data operations
- Configure field mapping for custom data structures
- Implement CRUD operations (Create, Read, Update, Delete)
- Handle data adapters and custom queries
Resources and Grouping
📄 Read: references/resources.md
Read this reference when you need to:
- Configure single or multiple resources (rooms, employees, equipment)
- Set up resource grouping (by date or by resource)
- Customize resource colors and styling
- Create resource templates
- Filter and manage resource visibility
- Handle resource-based events
Time Configuration
📄 Read: references/time-configuration.md
Read this reference when you need to:
- Configure timescale intervals and time slots
- Set start and end hours for day views
- Define working days and working hours
- Handle timezone support and conversions
- Manage multiple timezones
- Customize time formats and display
Editor Customization
📄 Read: references/editor-customization.md
Read this reference when you need to:
- Customize the event editor dialog
- Create custom editor templates
- Add custom fields to the editor
- Configure editor validation rules
- Customize quick info popups
- Handle editor events
Cell Customization
📄 Read: references/cell-customization.md
Read this reference when you need to:
- Create cell templates for custom rendering
- Customize date header templates
- Style time cells and work cells
- Configure month cell templates
- Create resource header templates
- Add custom content to cells
Recurrence Editor
📄 Read: references/recurrence-editor.md
Read this reference when you need to:
- Understand recurrence rule format (RRULE)
- Configure recurrence patterns (Daily, Weekly, Monthly, Yearly)
- Use the standalone recurrence editor component
- Handle FREQ, INTERVAL, BYDAY, BYMONTHDAY parameters
- Manage exception dates (EXDATE)
- Edit recurring event series or single occurrences
Header Customization
📄 Read: references/header-customization.md
Read this reference when you need to:
- Customize the header toolbar
- Add or remove toolbar items
- Create custom toolbar buttons
- Configure header rows
- Customize date range display
- Handle header events
Clipboard Operations
📄 Read: references/clipboard-operations.md
Read this reference when you need to:
- Enable copy (Ctrl+C), cut (Ctrl+X), paste (Ctrl+V) operations
- Configure clipboard settings
- Create custom clipboard actions
- Handle clipboard events
Exporting and Printing
📄 Read: references/exporting.md
Read this reference when you need to:
- Export events to ICS/iCal format
- Export scheduler to Excel
- Export scheduler to PDF
- Configure export options
- Implement print functionality
Styling and Themes
📄 Read: references/styling-themes.md
Read this reference when you need to:
- Apply built-in themes (Material, Bootstrap, Fabric, etc.)
- Customize component styling with CSS
- Configure custom event colors
- Style cells and views
- Create responsive designs
Advanced Features
📄 Read: references/advanced-features.md
Read this reference when you need to:
- Enable virtual scrolling for large datasets
- Configure lazy loading
- Set component dimensions (height, width)
- Enable row auto-height
- Handle drag-and-drop operations
- Resize events
- Manage multiple scheduler instances
State Persistence
📄 Read: references/state-persistence.md
Read this reference when you need to:
- Enable state persistence
- Configure which properties to persist
- Use localStorage integration
- Restore previous state
- Clear persisted state
Accessibility
📄 Read: references/accessibility.md
Read this reference when you need to:
- Implement WCAG 2.1 compliance
- Configure keyboard navigation
- Set up ARIA attributes
- Support screen readers
- Manage focus states
- Use high contrast themes
Localization and Internationalization
📄 Read: references/localization.md
Read this reference when you need to:
- Set up multi-language support
- Load culture files
- Enable RTL (Right-to-Left) support
- Customize date/time formatting
- Create custom localizations
Quick Start Example
Here's a minimal working example to get started with the Angular Scheduler:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, View } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, WorkWeekService, MonthService, AgendaService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, WorkWeekService, MonthService, AgendaService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [
{
Id: 1,
Subject: 'Team Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 11, 30)
},
{
Id: 2,
Subject: 'Project Review',
StartTime: new Date(2024, 0, 15, 14, 0),
EndTime: new Date(2024, 0, 15, 15, 30)
}
]
};
}CSS Import (in styles.css):
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-buttons/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-calendars/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-dropdowns/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-inputs/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-lists/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-popups/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-angular-schedule/styles/material3.css';Common Patterns
Pattern 1: Scheduler with Multiple Views
import { Component } from '@angular/core';
import { ScheduleModule, View } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService, TimelineViewsService, TimelineMonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService, TimelineViewsService, TimelineMonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[currentView]="currentView"
[views]="views"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public currentView: View = 'Week';
public views: View[] = ['Day', 'Week', 'Month', 'TimelineWeek', 'TimelineMonth'];
public eventSettings: object = { dataSource: [] };
}Pattern 2: Scheduler with Resources
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, GroupModel } from '@syncfusion/ej2-angular-schedule';
import { WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[group]="group"
[eventSettings]="eventSettings">
<e-resources>
<e-resource
field='RoomId'
title='Room'
name='Rooms'
[dataSource]='roomDataSource'
textField='RoomText'
idField='Id'
colorField='RoomColor'>
</e-resource>
</e-resources>
</ejs-schedule>
`
})
export class AppComponent {
public group: GroupModel = { resources: ['Rooms'] };
public roomDataSource: object[] = [
{ RoomText: 'Room 1', Id: 1, RoomColor: '#cb6bb2' },
{ RoomText: 'Room 2', Id: 2, RoomColor: '#56ca85' }
];
public eventSettings: EventSettingsModel = {
dataSource: [
{
Id: 1,
Subject: 'Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 11, 0),
RoomId: 1
}
]
};
}Pattern 3: Remote Data Binding
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
import { WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
crossDomain: true
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
}Pattern 4: Recurring Events
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [
{
Id: 1,
Subject: 'Daily Standup',
StartTime: new Date(2024, 0, 15, 9, 0),
EndTime: new Date(2024, 0, 15, 9, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=10' // Repeats daily for 10 days
},
{
Id: 2,
Subject: 'Weekly Review',
StartTime: new Date(2024, 0, 15, 14, 0),
EndTime: new Date(2024, 0, 15, 15, 0),
RecurrenceRule: 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=1' // Every Monday
}
]
};
}Key Configuration Options
Core Properties
| Property | Type | Description |
|---|---|---|
currentView | View | Active view mode (Day, Week, Month, etc.) |
selectedDate | Date | Currently selected date |
eventSettings | EventSettingsModel | Event data source and field mappings |
views | View[] | Array of views to display in the scheduler |
height | string | Scheduler height (e.g., '550px', '100%') |
width | string | Scheduler width (e.g., '100%', '800px') |
Event Settings
| Property | Type | Description |
|---|---|---|
dataSource | object[] \ | DataManager |
fields | FieldModel | Field mapping for custom data structure |
enableTooltip | boolean | Enable event tooltips |
template | string | Custom event template |
View Services (Must inject in providers)
DayService- Day viewWeekService- Week viewWorkWeekService- Work week viewMonthService- Month viewYearService- Year viewAgendaService- Agenda viewMonthAgendaService- Month agenda viewTimelineViewsService- Timeline day, week, work week viewsTimelineMonthService- Timeline month viewTimelineYearService- Timeline year view
Common Events
| Event | Description |
|---|---|
eventClick | Triggered when an event is clicked |
cellClick | Triggered when a cell is clicked |
cellDoubleClick | Triggered when a cell is double-clicked |
popupOpen | Triggered before editor/quick info popup opens |
actionBegin | Triggered before CRUD operations |
actionComplete | Triggered after CRUD operations |
dataBinding | Triggered before data binding |
dataBound | Triggered after data binding |
Common Use Cases
1. Appointment Booking System
Create a booking interface for healthcare, salons, or services with resource management, time slots, and availability checking.
Read: getting-started.md, resources.md, time-configuration.md
2. Meeting Room Scheduler
Manage multiple meeting rooms with resource grouping, conflict detection, and availability display.
Read: resources.md, appointments.md, data-binding.md
3. Project Timeline
Display project tasks in timeline views with drag-drop, resize, and milestone tracking.
Read: views-and-display.md, advanced-features.md, appointments.md
4. Team Calendar
Create a shared team calendar with recurring events, multiple timezones, and resource filtering.
Read: appointments.md, recurrence-editor.md, time-configuration.md, resources.md
5. Employee Shift Planning
Plan and manage employee shifts with working hours, multiple resources, and timeline views.
Read: time-configuration.md, resources.md, views-and-display.md
6. Event Management Portal
Build event management systems with export to calendar apps (ICS), custom editor forms, and templates.
Read: appointments.md, editor-customization.md, exporting.md
7. Multi-Timezone Calendar
Handle events across different timezones for global teams with timezone conversion and display.
Read: time-configuration.md, appointments.md
8. Custom Calendar Application
Create fully customized calendar applications with custom cells, templates, and styling.
Read: cell-customization.md, editor-customization.md, styling-themes.md
Accessibility
Table of Contents
- Keyboard Navigation
- General Navigation
- Cell Navigation
- Event Selection
- Enable Keyboard Interaction
- Screen Reader Support
- ARIA Attributes
- High Contrast Mode
- Color Contrast
- Focus Indicators
- Best Practices
The Scheduler is designed to be accessible to users with disabilities, supporting keyboard navigation, screen readers, and WCAG 2.2 compliance.
Keyboard Navigation
The Scheduler provides comprehensive keyboard support:
General Navigation
| Key | Action |
|---|---|
| Tab | Navigate between Scheduler elements |
| Shift + Tab | Navigate backwards |
| Enter | Open event editor or select element |
| Escape | Close popup or cancel action |
| Delete | Delete selected event |
Cell Navigation
| Key | Action |
|---|---|
| Arrow keys | Move between cells |
| Home | Navigate to first cell of row |
| End | Navigate to last cell of row |
| Page Up | Navigate to previous week/month |
| Page Down | Navigate to next week/month |
Event Selection
| Key | Action |
|---|---|
| Arrow keys | Navigate between events |
| Ctrl + Arrow keys | Move event (when selected) |
| Ctrl + C | Copy selected event |
| Ctrl + X | Cut selected event |
| Ctrl + V | Paste copied/cut event |
Enable Keyboard Interaction
Keyboard navigation is enabled by default:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[allowKeyboardInteraction]='true'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = { dataSource: [] };
}Screen Reader Support
The Scheduler includes ARIA attributes for screen reader compatibility:
- Role attributes: Proper ARIA roles for grid, row, gridcell
- Labels: Descriptive labels for interactive elements
- Live regions: Announce dynamic content changes
- Focus management: Logical focus order
ARIA Attributes
<!-- Automatically applied by Scheduler -->
<div role="grid" aria-label="Event Scheduler">
<div role="row">
<div role="gridcell" aria-label="Monday, January 15, 2024, 9:00 AM">
<div role="button" aria-label="Meeting with Team, 9:00 AM to 10:00 AM">
</div>
</div>
</div>
</div>High Contrast Mode
Support Windows High Contrast mode by importing the high contrast theme:
// In angular.json or styles.scss
@import '@syncfusion/ej2-angular-schedule/styles/highcontrast.css';Color Contrast
Ensure WCAG 2.2 Level AA compliance:
- Text contrast: Minimum 4.5:1 ratio for normal text
- Large text: Minimum 3:1 ratio for 18pt+ text
- UI components: Minimum 3:1 ratio for interactive elements
/* Ensure sufficient contrast */
.e-schedule .e-appointment {
color: #000000; /* Black text */
background-color: #FFEB3B; /* Yellow background */
/* Contrast ratio: 19.56:1 (WCAG AAA) */
}Focus Indicators
Provide visible focus indicators for keyboard users:
.e-schedule .e-work-cells:focus,
.e-schedule .e-appointment:focus {
outline: 2px solid #0078D4;
outline-offset: 2px;
}Best Practices
1. Keyboard Testing: Test all functionality with keyboard only 2. Screen Reader Testing: Verify with NVDA, JAWS, or VoiceOver 3. Focus Order: Ensure logical tab order 4. Error Messages: Provide accessible error announcements 5. Time Limits: Avoid automatic timeouts or provide extensions 6. Instructions: Provide keyboard shortcut documentation 7. Skip Links: Add skip navigation for complex layouts 8. Semantic HTML: Use proper heading levels and landmarks 9. Alt Text: Provide alternative text for icons 10. ARIA Labels: Use descriptive labels for dynamic content
Advanced Features
Table of Contents
- Virtual Scrolling
- Row Auto Height
- Dimensions Configuration
- Set Custom Width and Height
- Responsive Width
- Cell Dimensions via CSS
- Best Practices
Explore advanced Scheduler capabilities including virtual scrolling, row auto-height, and dimensions configuration.
Virtual Scrolling
Enable virtual scrolling for better performance with large datasets or extended timelines:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { TimelineViewsService, TimelineMonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [TimelineViewsService, TimelineMonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
<e-views>
<e-view option='TimelineMonth' [allowVirtualScrolling]='true' [isSelected]='true'></e-view>
</e-views>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = { dataSource: [] };
}Benefits:
- Renders only visible portion of timeline
- Improves performance with large date ranges
- Reduces initial load time
- Smooth scrolling experience
Supported Views:
- Timeline Day
- Timeline Week
- Timeline WorkWeek
- Timeline Month
Row Auto Height
Automatically adjust cell heights based on appointment count:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[rowAutoHeight]='true'>
<e-views>
<e-view option='Month'></e-view>
</e-views>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = { dataSource: [] };
}Behavior:
- Expands cells to fit all appointments
- Eliminates "+N more" indicator
- Better visibility of all events
- Especially useful in Month view
Dimensions Configuration
Set Custom Width and Height
<ejs-schedule
width='1200px'
height='650px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>Responsive Width
<ejs-schedule
width='100%'
height='100vh'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>Cell Dimensions via CSS
/* Customize cell height in vertical views */
.e-schedule .e-vertical-view .e-time-cells-wrap table td,
.e-schedule .e-vertical-view .e-work-cells {
height: 100px;
}
/* Customize column width */
.e-schedule .e-vertical-view .e-date-header-wrap table col,
.e-schedule .e-vertical-view .e-content-wrap table col {
width: 150px;
}Best Practices
1. Virtual Scrolling: Enable for Timeline views with intervals > 3 months 2. Row Auto Height: Use when all events must be visible without interaction 3. Fixed Dimensions: Use pixel values for fixed layouts (dashboards) 4. Responsive Dimensions: Use percentage/vh for full-page applications 5. Cell Height: Balance between readability and screen space 6. Performance: Test with realistic data volumes before enabling features 7. Mobile: Disable row auto height on mobile to prevent excessive scrolling 8. Memory: Monitor memory usage with virtual scrolling enabled 9. Combination: Can combine virtual scrolling with other performance optimizations 10. Testing: Verify scrolling behavior across different browsers
Appointments and Event Management
Table of Contents
- Overview
- Event Types
- Normal Events
- Spanned Events
- All-Day Events
- Recurring Events
- Event Fields
- Field Mapping
- Recurrence Rules
- Recurrence Patterns
- Exception Dates
- Editing Recurring Events
- Recurrence Validation
Overview
Appointments (events) in the Scheduler represent scheduled items for specific time periods. The Scheduler categorizes events based on their duration and recurrence pattern, providing flexible event management capabilities.
Event Types
The Scheduler supports four types of events:
| Type | Description | Display Location |
|---|---|---|
| Normal Events | Appointments for specific time intervals within a day | Regular time cells |
| Spanned Events | Events lasting more than 24 hours | All-day row (default) or time slots |
| All-Day Events | Full-day events like holidays | All-day row |
| Recurring Events | Events that repeat at regular intervals | Regular cells with repeat indicator |
Normal Events
Normal events are appointments created for specific time intervals within a day (less than 24 hours).
Creating Normal Events
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService, AgendaService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService, AgendaService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public data: object[] = [
{
Id: 1,
Subject: 'Team Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 12, 30)
},
{
Id: 2,
Subject: 'Client Call',
StartTime: new Date(2024, 0, 15, 14, 0),
EndTime: new Date(2024, 0, 15, 15, 0),
Location: 'Conference Room',
Description: 'Discuss project requirements'
}
];
public eventSettings: EventSettingsModel = {
dataSource: this.data
};
}Spanned Events
Spanned events are appointments with durations longer than 24 hours. By default, they display in the all-day row.
Customize Spanned Event Placement
Use the spannedEventPlacement property to control where spanned events display:
Options:
'AllDayRow'(default) - Display in all-day row'TimeSlot'- Display in regular time cells
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public data: object[] = [
{
Id: 1,
Subject: 'Conference',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 17, 12, 30), // Spans 2+ days
IsAllDay: false
},
{
Id: 2,
Subject: 'Training',
StartTime: new Date(2024, 0, 16, 12, 0),
EndTime: new Date(2024, 0, 18, 13, 0), // Spans 2+ days
IsAllDay: false
}
];
public eventSettings: EventSettingsModel = {
dataSource: this.data,
spannedEventPlacement: 'TimeSlot' // Display in time cells instead of all-day row
};
}Note: Events spanning multiple days but less than 24 hours (e.g., 11 PM to 2 AM) are split and displayed on each day.
All-Day Events
All-day events represent full-day appointments like holidays. They display in a separate all-day row.
Creating All-Day Events
Set IsAllDay: true to mark an event as all-day:
public data: object[] = [
{
Id: 1,
Subject: 'Company Holiday',
StartTime: new Date(2024, 0, 15, 0, 0),
EndTime: new Date(2024, 0, 15, 23, 59),
IsAllDay: true
},
{
Id: 2,
Subject: 'Team Building Event',
StartTime: new Date(2024, 0, 16, 0, 0),
EndTime: new Date(2024, 0, 18, 0, 0), // Multi-day all-day event
IsAllDay: true
}
];Hide All-Day Row
Use CSS to hide the all-day row:
.e-schedule .e-date-header-wrap .e-schedule-table thead {
display: none;
}Expand All-Day Row on Load
Automatically expand the all-day row to show all events:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
let initialLoad = true;
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#scheduleObj
width='100%'
height='750px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings"
(dataBound)="onDataBound()">
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('scheduleObj') public scheduleObj?: ScheduleComponent;
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = { dataSource: [] };
onDataBound() {
if (initialLoad) {
const elements: HTMLElement = this.scheduleObj?.element.querySelector(
'.e-all-day-appointment-section'
) as HTMLElement;
elements.click();
initialLoad = false;
}
}
}Recurring Events
Recurring events repeat at regular intervals based on a recurrence rule following the iCalendar RFC 5545 standard.
Creating Recurring Events
Use the RecurrenceRule field to define the recurrence pattern:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public data: object[] = [
{
Id: 1,
Subject: 'Daily Standup',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 10, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=10' // Daily for 10 days
},
{
Id: 2,
Subject: 'Weekly Review',
StartTime: new Date(2024, 0, 15, 14, 0),
EndTime: new Date(2024, 0, 15, 15, 0),
RecurrenceRule: 'FREQ=WEEKLY;BYDAY=MO;INTERVAL=1' // Every Monday
},
{
Id: 3,
Subject: 'Monthly Report',
StartTime: new Date(2024, 0, 1, 9, 0),
EndTime: new Date(2024, 0, 1, 10, 0),
RecurrenceRule: 'FREQ=MONTHLY;BYMONTHDAY=1;INTERVAL=1;COUNT=12' // 1st of each month
}
];
public eventSettings: EventSettingsModel = {
dataSource: this.data
};
}Visual Indicator: Recurring events display with a repeat icon in the lower-right corner.
Event Fields
Events require specific fields to be properly displayed and managed in the Scheduler.
Built-in Event Fields
| Field | Type | Required | Description |
|---|---|---|---|
Id | number/string | Yes | Unique event identifier |
Subject | string | Yes | Event title/summary |
StartTime | Date | Yes | Event start date and time |
EndTime | Date | Yes | Event end date and time |
IsAllDay | boolean | No | Mark as all-day event |
Location | string | No | Event location |
Description | string | No | Event description/notes |
RecurrenceRule | string | No | Recurrence pattern (RRULE format) |
RecurrenceID | number | No | Parent event ID for edited occurrences |
RecurrenceException | string | No | Exception dates (ISO format) |
FollowingID | number | No | Parent ID for "edit following" scenarios |
StartTimezone | string | No | Start timezone (IANA format) |
EndTimezone | string | No | End timezone (IANA format) |
IsReadonly | boolean | No | Make event read-only |
IsBlock | boolean | No | Block time slot (prevents event creation) |
Default Field Names
When using default field names, no mapping is required:
public data: object[] = [
{
Id: 1,
Subject: 'Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 12, 0),
Location: 'Room 101',
Description: 'Project kickoff meeting',
IsAllDay: false
}
];Field Mapping
Map custom field names to Scheduler field names using the fields property:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public data: object[] = [
{
TravelId: 1,
TravelSummary: 'Business Trip to Paris',
DepartureTime: new Date(2024, 0, 15, 10, 0),
ArrivalTime: new Date(2024, 0, 15, 12, 30),
FullDay: false,
Source: 'London',
Comments: 'Client meeting',
Origin: 'Europe/London',
Destination: 'Europe/Paris',
IsDisabled: false
}
];
public eventSettings: EventSettingsModel = {
dataSource: this.data,
fields: {
id: 'TravelId',
subject: { name: 'TravelSummary' },
startTime: { name: 'DepartureTime' },
endTime: { name: 'ArrivalTime' },
isAllDay: { name: 'FullDay' },
location: { name: 'Source' },
description: { name: 'Comments' },
startTimezone: { name: 'Origin' },
endTimezone: { name: 'Destination' },
isBlock: 'IsDisabled'
}
};
}Field Settings with Additional Options
Field mappings can include additional options:
fields: {
id: 'EventId',
subject: {
name: 'Title',
default: 'No Title', // Default value
title: 'Event Title', // Label in editor
validation: { required: true } // Validation rules
},
startTime: {
name: 'Start',
validation: { required: true }
},
endTime: {
name: 'End',
validation: { required: true }
},
location: {
name: 'Place',
title: 'Location',
default: 'Office'
},
description: {
name: 'Notes',
title: 'Description'
}
}Field Setting Options:
name: Field name in data sourcedefault: Default value when field is emptytitle: Label displayed in event editorvalidation: Validation rules for editor
Recurrence Rules
Recurrence rules follow the iCalendar RFC 5545 standard and define how events repeat.
Recurrence Rule Properties
| Property | Description | Example |
|---|---|---|
FREQ | Frequency type (DAILY, WEEKLY, MONTHLY, YEARLY) | FREQ=DAILY |
INTERVAL | Interval between occurrences | INTERVAL=2 (every 2 days) |
COUNT | Number of occurrences | COUNT=10 (10 times) |
UNTIL | End date in ISO format | UNTIL=20241231T235959Z |
BYDAY | Days of week (MO, TU, WE, TH, FR, SA, SU) | BYDAY=MO,WE,FR |
BYMONTHDAY | Day of month (1-31) | BYMONTHDAY=15 |
BYMONTH | Month number (1-12) | BYMONTH=6 (June) |
BYSETPOS | Position in month (1-5, -1 for last) | BYSETPOS=2 (second occurrence) |
Basic Recurrence Rule Format
FREQ=[DAILY|WEEKLY|MONTHLY|YEARLY];[INTERVAL=n;][COUNT=n|UNTIL=date;][Additional properties]Recurrence Patterns
Daily Recurrence
// Every day, never ends
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1'
// Every day for 5 occurrences
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=5'
// Every day until December 31, 2024
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;UNTIL=20241231T235959Z'
// Every 2 days for 10 occurrences
RecurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=10'Weekly Recurrence
// Every Monday, Wednesday, Friday
RecurrenceRule: 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR'
// Every Thursday for 10 occurrences
RecurrenceRule: 'FREQ=WEEKLY;INTERVAL=1;BYDAY=TH;COUNT=10'
// Every Monday until end of year
RecurrenceRule: 'FREQ=WEEKLY;INTERVAL=1;BYDAY=MO;UNTIL=20241231T235959Z'
// Every 2 weeks on Monday, Wednesday, Friday for 10 times
RecurrenceRule: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR;COUNT=10'Monthly Recurrence
// 15th day of every month
RecurrenceRule: 'FREQ=MONTHLY;BYMONTHDAY=15;INTERVAL=1'
// 16th day of every month for 10 occurrences
RecurrenceRule: 'FREQ=MONTHLY;BYMONTHDAY=16;INTERVAL=1;COUNT=10'
// 17th day of every month until December 2024
RecurrenceRule: 'FREQ=MONTHLY;BYMONTHDAY=17;INTERVAL=1;UNTIL=20241231T235959Z'
// 2nd Friday of every month
RecurrenceRule: 'FREQ=MONTHLY;BYDAY=FR;BYSETPOS=2;INTERVAL=1'
// Last day of every month
RecurrenceRule: 'FREQ=MONTHLY;BYMONTHDAY=-1;INTERVAL=1'
// 4th Wednesday of every month for 10 occurrences
RecurrenceRule: 'FREQ=MONTHLY;BYDAY=WE;BYSETPOS=4;INTERVAL=1;COUNT=10'Yearly Recurrence
// December 15th every year
RecurrenceRule: 'FREQ=YEARLY;BYMONTHDAY=15;BYMONTH=12;INTERVAL=1'
// December 10th for 10 years
RecurrenceRule: 'FREQ=YEARLY;BYMONTHDAY=10;BYMONTH=12;INTERVAL=1;COUNT=10'
// December 12th until 2025
RecurrenceRule: 'FREQ=YEARLY;BYMONTHDAY=12;BYMONTH=12;INTERVAL=1;UNTIL=20251212T235959Z'
// 3rd Friday of December every year
RecurrenceRule: 'FREQ=YEARLY;BYDAY=FR;BYMONTH=12;BYSETPOS=3;INTERVAL=1'
// Last Tuesday of December for 10 years
RecurrenceRule: 'FREQ=YEARLY;BYDAY=TU;BYMONTH=12;BYSETPOS=-1;INTERVAL=1;COUNT=10'Exception Dates
Exclude specific occurrences from a recurring series using RecurrenceException:
public data: object[] = [
{
Id: 1,
Subject: 'Daily Meeting',
StartTime: new Date(2024, 0, 28, 10, 0),
EndTime: new Date(2024, 0, 28, 10, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=8',
RecurrenceException: '20240129T100000Z,20240131T100000Z,20240202T100000Z' // Skip Jan 29, 31, Feb 2
}
];Exception Date Format:
- Use ISO 8601 format without hyphens
- UTC times end with 'Z'
- Example:
20240129T100000Zfor January 29, 2024 at 10:00 AM UTC - Multiple exceptions separated by commas
Editing Recurring Events
Edit Single Occurrence
Create a new event with RecurrenceID pointing to the parent:
public data: object[] = [
{
Id: 1,
Subject: 'Scrum Meeting',
StartTime: new Date(2024, 0, 28, 10, 0),
EndTime: new Date(2024, 0, 28, 10, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;COUNT=8',
RecurrenceException: '20240130T100000Z' // Exclude Jan 30
},
{
Id: 2,
Subject: 'Scrum Meeting (Rescheduled)',
StartTime: new Date(2024, 0, 30, 9, 0), // Different time
EndTime: new Date(2024, 0, 30, 9, 30),
Description: 'Rescheduled due to conflict',
RecurrenceID: 1 // Points to parent event
}
];Steps: 1. Add exception date to parent's RecurrenceException 2. Create new event with different time 3. Set RecurrenceID to parent's Id
Edit Following Events
Enable editing current and following events:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 28);
public data: object[] = [
{
Id: 1,
Subject: 'Scrum Meeting',
StartTime: new Date(2024, 0, 28, 10, 0),
EndTime: new Date(2024, 0, 28, 10, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;UNTIL=20240129T100000Z' // Ends Jan 29
},
{
Id: 2,
Subject: 'Scrum Meeting - Updated',
StartTime: new Date(2024, 0, 30, 10, 0),
EndTime: new Date(2024, 0, 30, 10, 30),
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;UNTIL=20240204T100000Z', // New end date
FollowingID: 1 // Points to immediate parent
}
];
public eventSettings: EventSettingsModel = {
dataSource: this.data,
editFollowingEvents: true // Enable edit following feature
};
}Steps: 1. Update parent's RecurrenceRule with UNTIL date before edited occurrence 2. Create new event with FollowingID pointing to parent 3. Set editFollowingEvents: true in eventSettings
Recurrence Validation
The Scheduler includes built-in validation for recurring events:
Validation Messages
| Message | Cause |
|---|---|
| "The recurrence pattern is not valid" | Invalid rule (e.g., end date before start date) |
| "The changes made to specific instances will be cancelled" | Editing series when occurrences were already edited |
| "The duration must be shorter than frequency" | Event duration exceeds recurrence interval |
| "Some months have fewer than the selected date" | Date doesn't exist in all months (e.g., 31st) |
| "Two occurrences cannot occur on the same day" | Moving occurrence to date with existing occurrence |
Example Validation Scenarios
Invalid Pattern:
// Error: End date before start date
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1;UNTIL=20231231T235959Z'
StartTime: new Date(2024, 0, 1, 10, 0)Duration Exceeds Frequency:
// Error: 2-day event with daily recurrence
StartTime: new Date(2024, 0, 1, 10, 0)
EndTime: new Date(2024, 0, 3, 10, 0) // 2 days duration
RecurrenceRule: 'FREQ=DAILY;INTERVAL=1' // Repeats dailyBest Practices
1. Always Provide Required Fields: Id, StartTime, and EndTime are mandatory 2. Use ISO Format for Exceptions: Format exception dates as YYYYMMDDTHHmmssZ 3. Test Recurrence Rules: Validate rules using iCalendar standards 4. Handle Timezones: Use StartTimezone and EndTimezone for multi-timezone scenarios 5. Map Custom Fields: Use fields property when data structure differs from defaults 6. Validate Edit Operations: Be aware of validation messages when editing recurring events 7. Use COUNT or UNTIL: Always specify an end condition for recurring events 8. Block Time Slots: Use IsBlock: true to prevent event creation on specific slots
Common Issues
Events not displaying
- Solution: Verify
Id,StartTime, andEndTimeare provided and valid
Recurrence not working
- Solution: Check
RecurrenceRulesyntax; must follow iCalendar RFC 5545 format
Custom fields not mapping
- Solution: Ensure
fieldsproperty ineventSettingsmaps all custom field names
Exception dates not excluding occurrences
- Solution: Use correct ISO format without hyphens (e.g.,
20240129T100000Z)
Edited occurrences not displaying
- Solution: Verify
RecurrenceIDmatches parent event'sIdand exception date is added to parent
Cell Customization
Table of Contents
- Cell Dimensions
- Customize Cell Size with CSS
- Cell Availability Check
- Validate Slot Availability
- Cell Templates
- Apply Custom Content with cellTemplate
- renderCell Event
- Customize Cells Using renderCell Event
- Element Types for renderCell
- Customize Weekend Cell Background
- Month View Cell Header
- Customize Month Cell Header
- Date Range Restrictions
- Set Minimum and Maximum Dates
- Multiple Cell Selection
- Disable Multiple Selection
- Best Practices
- Common Scenarios
- Highlight Special Dates
- Disable Specific Cells
- Department-Specific Colors
- Time Slot Restrictions
- Custom Date Formats
Customize Scheduler cell appearance, dimensions, and behavior using templates, events, and CSS.
Cell Dimensions
Customize Cell Size with CSS
Control the height and width of cells using the cssClass property:
import { Component, ViewEncapsulation } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
cssClass='schedule-cell-dimension'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`,
styles: [`
.schedule-cell-dimension.e-schedule .e-vertical-view .e-date-header-wrap table col,
.schedule-cell-dimension.e-schedule .e-vertical-view .e-content-wrap table col {
width: 200px;
}
.schedule-cell-dimension.e-schedule .e-vertical-view .e-time-cells-wrap table td,
.schedule-cell-dimension.e-schedule .e-vertical-view .e-work-cells {
height: 100px;
}
.schedule-cell-dimension.e-schedule .e-month-view .e-work-cells,
.schedule-cell-dimension.e-schedule .e-month-view .e-date-header-wrap table col {
width: 200px;
}
.schedule-cell-dimension.e-schedule .e-month-view .e-work-cells {
height: 200px;
}
`],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}CSS Targets:
- Vertical Views (Day/Week):
- Column width:
.e-vertical-view .e-content-wrap table col - Row height:
.e-vertical-view .e-work-cells - Time cell height:
.e-vertical-view .e-time-cells-wrap table td - Month View:
- Cell width:
.e-month-view .e-date-header-wrap table col - Cell height:
.e-month-view .e-work-cells
Cell Availability Check
Validate Slot Availability
Check if a time slot is available before allowing event creation using isSlotAvailable method:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel, ActionEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { EventFieldsMapping } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
(actionBegin)='onActionBegin($event)'>
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onActionBegin(args: ActionEventArgs): void {
if (args.requestType === 'eventCreate' && (args.data as Object[]).length > 0) {
const eventData = (args.data as Object[])[0] as { [key: string]: any };
const eventField = this.scheduleObj.eventFields as EventFieldsMapping;
const startDate: Date = eventData[eventField.startTime || ''] as Date;
const endDate: Date = eventData[eventField.endTime || ''] as Date;
// Cancel event creation if slot is not available
args.cancel = !this.scheduleObj.isSlotAvailable(startDate, endDate);
}
}
}Important Notes:
isSlotAvailableverifies appointments only within the current view's date range- Does not evaluate recurrence occurrences beyond the visible date range
- Returns
trueif slot is free,falseif occupied
Cell Templates
Apply Custom Content with cellTemplate
Customize cell backgrounds with images, text, or HTML using cellTemplate:
import { Component, ViewEncapsulation } from '@angular/core';
import { ScheduleModule, View } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule, CommonModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
cssClass='schedule-cell-template'
[selectedDate]='selectedDate'
[(currentView)]='currentView'>
<ng-template #cellTemplate let-data>
<div class="templatewrap" *ngIf="data.type == 'workCells'" [innerHTML]="getWorkCellText(data.date)"></div>
<div class="templatewrap" *ngIf="data.type == 'monthCells'" [innerHTML]="getMonthCellText(data.date)"></div>
</ng-template>
</ejs-schedule>
`,
styles: [`
.schedule-cell-template.e-schedule .e-month-view .e-work-cells {
position: relative;
}
.templatewrap {
text-align: center;
position: absolute;
width: 100%;
}
.templatewrap img {
width: 20px;
height: 20px;
}
`],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 11, 16);
public currentView: View = 'Week';
getMonthCellText(date: Date): string {
if (date.getMonth() === 10 && date.getDate() === 23) {
return '<img src="url" />';
} else if (date.getMonth() === 11 && date.getDate() === 9) {
return '<img src="url" />';
} else if (date.getMonth() === 11 && date.getDate() === 13) {
return '<img src="url" />';
} else if (date.getMonth() === 11 && date.getDate() === 22) {
return '<img src="url" />';
} else if (date.getMonth() === 11 && date.getDate() === 24) {
return '<img src="url" />';
} else if (date.getMonth() === 11 && date.getDate() === 25) {
return '<img src="url" />';
} else if (date.getMonth() === 0 && date.getDate() === 1) {
return '<img src="url" />';
} else if (date.getMonth() === 0 && date.getDate() === 14) {
return '<img src="url" />';
}
return '';
}
getWorkCellText(date: Date): string {
const weekEnds: number[] = [0, 6];
if (weekEnds.indexOf(date.getDay()) >= 0) {
return "<img src='url' />";
}
return '';
}
}Template Context:
data.date: Current cell's datedata.type: Cell type ('workCells','monthCells', etc.)- Returns HTML string for rendering
renderCell Event
Customize Cells Using renderCell Event
Alternative to templates for programmatic cell customization:
import { Component, ViewEncapsulation } from '@angular/core';
import { ScheduleModule, EventSettingsModel, RenderCellEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { createElement } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
currentView='Month'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
(renderCell)='onRenderCell($event)'>
</ejs-schedule>
`,
styles: [`
.templatewrap {
text-align: center;
width: 100%;
}
.templatewrap img {
width: 20px;
height: 20px;
}
`],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 14);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onRenderCell(args: RenderCellEventArgs): void {
if (args.elementType === 'workCells' || args.elementType === 'monthCells') {
const weekEnds: number[] = [0, 6];
if (args.date && weekEnds.indexOf(args.date.getDay()) >= 0) {
const ele: HTMLElement = createElement('div', {
innerHTML: "<img src='url' />",
className: 'templatewrap'
});
args.element.appendChild(ele);
}
}
}
}Element Types for renderCell
Check args.elementType to target specific cell types:
| Element Type | Description |
|---|---|
dateHeader | Header cell rendering |
monthDay | Header cell in Month view |
resourceHeader | Resource header cell |
alldayCells | All-day cell rendering |
emptyCells | Empty cell on header bar |
resourceGroupCells | Work cells for parent resource |
workCells | Work cell rendering |
monthCells | Month cell rendering |
majorSlot | Major time slot cell |
minorSlot | Minor time slot cell |
weekNumberCell | Cell displaying week number |
Customize Weekend Cell Background
Change background color of weekend cells:
import { Component, ViewEncapsulation } from '@angular/core';
import { ScheduleModule, EventSettingsModel, RenderCellEventArgs, View } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='650px'
cssClass='schedule-cell-customization'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[(currentView)]='currentView'
(renderCell)='onRenderCell($event)'>
</ejs-schedule>
`,
styles: [`
.schedule-cell-customization.e-schedule .e-month-view .e-work-cells:not(.e-work-days) {
background-color: #f08080;
}
`],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public currentView: View = 'Week';
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onRenderCell(args: RenderCellEventArgs): void {
if (args.elementType === 'workCells') {
// Change color of weekend columns in Week view
if (args.date) {
if (args.date.getDay() === 6) {
(args.element as any).style.background = '#ffdea2';
}
if (args.date.getDay() === 0) {
(args.element as any).style.background = '#ffdea2';
}
}
}
}
}CSS Approach for Month View:
- Use
.e-work-cells:not(.e-work-days)selector for weekend cells renderCellevent for Week/Day views- Combine both for comprehensive coverage
Month View Cell Header
Customize Month Cell Header
Use cellHeaderTemplate to customize date headers in Month view:
import { Component, ViewEncapsulation } from '@angular/core';
import { ScheduleModule } from '@syncfusion/ej2-angular-schedule';
import { MonthService } from '@syncfusion/ej2-angular-schedule';
import { Internationalization } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
cssClass='schedule-cell-header-template'>
<ng-template #cellHeaderTemplate let-data>
<div class="date-text">{{getDateHeaderText(data.date)}}</div>
</ng-template>
<e-views>
<e-view option='Month'></e-view>
</e-views>
</ejs-schedule>
`,
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
private instance: Internationalization = new Internationalization();
getDateHeaderText = (value: Date): string => {
return this.instance.formatDate(value, { skeleton: 'Ed' });
};
}Template Context:
data.date: Cell's date value- Use
Internationalizationfor locale-aware formatting - Only applies to Month view
Date Range Restrictions
Set Minimum and Maximum Dates
Restrict navigation to specific date range using minDate and maxDate:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, View } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[minDate]='minDate'
[maxDate]='maxDate'
[currentView]='currentView'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 17);
public minDate: Date = new Date(2023, 3, 17);
public maxDate: Date = new Date(2025, 7, 17);
public currentView: View = 'Month';
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}Default Values:
minDate:new Date(1900, 0, 1)maxDate:new Date(2099, 11, 31)- Dates beyond range are disabled for navigation
Multiple Cell Selection
Disable Multiple Selection
Control multi-cell and multi-row selection behavior:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[allowMultiCellSelection]='false'
[allowMultiRowSelection]='false'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}Properties:
allowMultiCellSelection: Enable/disable multi-cell selection (default:true)allowMultiRowSelection: Enable/disable multi-row selection (default:true)
Best Practices
1. CSS Classes: Use cssClass for dimension changes to maintain consistency 2. Performance: Avoid heavy computations in renderCell event (fires for every cell) 3. Templates vs Events: Use templates for static content, events for dynamic logic 4. Position: Set parent position to relative when using absolute-positioned templates 5. Element Types: Check elementType to avoid applying styles to wrong cells 6. Weekend Styling: Combine CSS and renderCell for comprehensive coverage 7. Date Range: Set minDate and maxDate for restricted scheduling scenarios 8. Slot Availability: Use isSlotAvailable to prevent double booking 9. Cell Headers: Use cellHeaderTemplate for Month view customization 10. Selection: Disable multi-selection for simpler UX in single-event scenarios
Common Scenarios
Highlight Special Dates
Use cellTemplate with date checks to display icons or colors for holidays, birthdays, or events.
Disable Specific Cells
Apply CSS class in renderCell to visually disable and prevent interaction with certain cells.
Department-Specific Colors
Color-code cells based on resource or department using renderCell event.
Time Slot Restrictions
Combine isSlotAvailable with custom logic to enforce business rules (e.g., no evening appointments).
Custom Date Formats
Use cellHeaderTemplate with Internationalization for locale-specific date displays.
Clipboard Operations
Table of Contents
- Enable Clipboard Functionality
- Keyboard Shortcuts
- Programmatic Methods
- copy Method
- cut Method
- paste Method
- Context Menu Integration
- Modify Content Before Pasting
- Best Practices
- Common Scenarios
- Disable Paste for Specific Times
- Copy from Grid to Scheduler
- Duplicate with Time Offset
- Add Metadata on Copy
- Restrict Cut Permission
Enable users to cut, copy, and paste appointments using keyboard shortcuts or programmatic methods for efficient schedule management.
Enable Clipboard Functionality
Activate clipboard features by setting the allowClipboard property to true:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { extend } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[allowClipboard]='true'
[showQuickInfo]='false'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: extend([], [], undefined, true) as Record<string, any>[]
};
}Prerequisites:
allowKeyboardInteractionmust betrue(default)- Optionally disable
showQuickInfofor cleaner UX
Keyboard Shortcuts
| Operation | Windows/Linux | Mac | Description |
|---|---|---|---|
| Copy | Ctrl+C | Cmd+C | Duplicate appointment to clipboard |
| Cut | Ctrl+X | Cmd+X | Move appointment to clipboard (removes from current slot) |
| Paste | Ctrl+V | Cmd+V | Insert copied/cut appointment into selected slot |
Usage Flow: 1. Click an appointment to select it 2. Press Ctrl+C (copy) or Ctrl+X (cut) 3. Click a target time slot 4. Press Ctrl+V to paste
Programmatic Methods
copy Method
Duplicate the selected appointment to clipboard:
copy(appointments?: Record<string, any>[] | Record<string, any>): voidcut Method
Remove the selected appointment and move to clipboard:
cut(appointments?: Record<string, any>[] | Record<string, any>): voidpaste Method
Insert clipboard content into a target cell:
paste(targetElement: HTMLElement): voidParameters:
appointments: Appointment object(s) to copy/cut (optional, uses selection if omitted)targetElement: Scheduler work cell where appointment will be pasted
Context Menu Integration
Implement clipboard operations via context menu:
import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { ContextMenuComponent, ContextMenuModule, MenuItemModel, BeforeOpenCloseMenuEventArgs, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { closest, isNullOrUndefined, remove, extend } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule, ContextMenuModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#scheduleObj
height='550px'
[selectedDate]='selectedDate'
[allowClipboard]='true'
[showQuickInfo]='false'
[eventSettings]='eventSettings'>
</ejs-schedule>
<ejs-contextmenu
#menuObj
cssClass='schedule-context-menu'
target='.e-schedule'
[items]='menuItems'
(beforeOpen)='onContextMenuBeforeOpen($event)'
(select)='onMenuItemSelect($event)'>
</ejs-contextmenu>
`,
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
@ViewChild('scheduleObj') public scheduleObj!: ScheduleComponent;
@ViewChild('menuObj') public menuObj!: ContextMenuComponent;
public selectedDate: Date = new Date(2024, 0, 15);
public selectedTarget!: Element;
public targetElement!: HTMLElement;
public eventSettings: EventSettingsModel = {
dataSource: extend([], [], undefined, true) as Record<string, any>[]
};
public menuItems: MenuItemModel[] = [
{ text: 'Cut Event', iconCss: 'e-icons e-cut', id: 'Cut' },
{ text: 'Copy Event', iconCss: 'e-icons e-copy', id: 'Copy' },
{ text: 'Paste', iconCss: 'e-icons e-paste', id: 'Paste' }
];
onContextMenuBeforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
const newEventElement: HTMLElement = document.querySelector('.e-new-event') as HTMLElement;
if (newEventElement) {
remove(newEventElement);
}
this.scheduleObj.closeQuickInfoPopup();
this.targetElement = args.event.target as HTMLElement;
if (closest(this.targetElement, '.e-contextmenu')) {
return;
}
this.selectedTarget = closest(this.targetElement, '.e-appointment,.e-work-cells,' +
'.e-vertical-view .e-date-header-wrap .e-all-day-cells,.e-vertical-view .e-date-header-wrap .e-header-cells') as Element;
if (isNullOrUndefined(this.selectedTarget)) {
args.cancel = true;
return;
}
if (this.selectedTarget.classList.contains('e-appointment')) {
this.menuObj.showItems(['Cut', 'Copy'], true);
this.menuObj.hideItems(['Paste'], true);
} else {
this.menuObj.showItems(['Paste'], true);
this.menuObj.hideItems(['Cut', 'Copy'], true);
}
}
onMenuItemSelect(args: MenuEventArgs): void {
const selectedMenuItem: string = args.item.id as string;
switch (selectedMenuItem) {
case 'Cut':
this.scheduleObj.cut([this.selectedTarget] as HTMLElement[]);
break;
case 'Copy':
this.scheduleObj.copy([this.selectedTarget] as HTMLElement[]);
break;
case 'Paste':
this.scheduleObj.paste(this.targetElement);
break;
}
}
}Context Menu Logic:
- Show Cut/Copy when right-clicking appointment
- Show Paste when right-clicking empty cell
- Hide irrelevant options dynamically
Modify Content Before Pasting
Intercept and modify appointment data before pasting using beforePaste event:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, BeforePasteEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[allowClipboard]='true'
[showQuickInfo]='false'
(beforePaste)='onBeforePaste($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: []
};
onBeforePaste(args: BeforePasteEventArgs): void {
// Modify appointment data before pasting
if (args.data) {
const data = args.data as any[];
data.forEach((appointment: any) => {
// Add custom modifications
appointment.Description = appointment.Description + ' (Copied)';
appointment.Location = appointment.Location || 'Default Location';
// Adjust time by 1 hour
appointment.StartTime = new Date(appointment.StartTime.getTime() + 60 * 60 * 1000);
appointment.EndTime = new Date(appointment.EndTime.getTime() + 60 * 60 * 1000);
});
}
}
}beforePaste Event Args:
data: Array of appointment objects being pastedcancel: Set totrueto prevent paste operation- Modify
datadirectly to change appointment properties
Best Practices
1. Keyboard Focus: Ensure allowKeyboardInteraction: true for shortcuts 2. Context Menu: Provide visual clipboard operations for discoverability 3. beforePaste: Validate and sanitize data before pasting 4. Quick Info: Disable (showQuickInfo: false) to avoid popup conflicts 5. Field Mapping: Ensure pasted data matches eventSettings.fields structure 6. Time Validation: Check for conflicts in beforePaste event 7. User Feedback: Show toast notifications after successful clipboard operations 8. Multi-Selection: Use array parameter in copy/cut for batch operations 9. Cancel Paste: Set args.cancel = true in beforePaste to prevent invalid pastes 10. Cross-Application: Use beforePaste to transform external data (e.g., from Grid)
Common Scenarios
Disable Paste for Specific Times
Check target cell time in beforePaste and cancel if outside business hours.
Copy from Grid to Scheduler
Map grid columns to scheduler fields in beforePaste event.
Duplicate with Time Offset
Add 1 hour to start/end times when pasting to avoid overlap.
Add Metadata on Copy
Append "Copy of" to subject or add creation timestamp.
Restrict Cut Permission
Check user role before allowing cut operation via context menu logic.
Data Binding and CRUD Operations
Table of Contents
- Overview
- Local Data Binding
- Basic Local Data Binding
- Using DataManager with Local Data
- Remote Data Binding
- Using ODataV4 Adaptor
- Using Web API Adaptor
- Filter Events with Built-in Query
- Using URL Adaptor for CRUD
- Loading Data via AJAX
- CRUD Operations
- Adding Events
- Updating Events
- Deleting Events
- Server-Side CRUD Actions
- Server-Side Insert
- Server-Side Update
- Server-Side Delete
- Custom Adaptor
- Additional Parameters
- Error Handling
- Best Practices
- Common Issues
- Events not loading
- CRUD operations not working
- Events not updating in UI
- Cross-origin errors
- Custom fields not saving
Overview
The Scheduler supports binding to local and remote data sources using the DataManager, which provides a unified interface for data operations. The dataSource property in eventSettings accepts either a JavaScript array or a DataManager instance.
Supported Data Binding Methods:
- Local data (JavaScript arrays)
- Remote data (REST APIs, OData services)
- CRUD operations (Create, Read, Update, Delete)
Local Data Binding
Bind JavaScript object arrays directly to the Scheduler.
Basic Local Data Binding
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, WorkWeekService, MonthService, AgendaService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, WorkWeekService, MonthService, AgendaService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [
{
Id: 1,
Subject: 'Team Meeting',
StartTime: new Date(2024, 0, 15, 9, 30),
EndTime: new Date(2024, 0, 15, 11, 0)
},
{
Id: 2,
Subject: 'Project Review',
StartTime: new Date(2024, 0, 16, 12, 0),
EndTime: new Date(2024, 0, 16, 14, 0)
},
{
Id: 3,
Subject: 'Client Presentation',
StartTime: new Date(2024, 0, 17, 9, 30),
EndTime: new Date(2024, 0, 17, 11, 0)
},
{
Id: 4,
Subject: 'Code Review',
StartTime: new Date(2024, 0, 18, 13, 0),
EndTime: new Date(2024, 0, 18, 14, 30)
}
]
};
}Note: By default, DataManager uses JsonAdaptor for local data.
Using DataManager with Local Data
import { DataManager } from '@syncfusion/ej2-data';
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
private localData: object[] = [
{
Id: 1,
Subject: 'Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 11, 0)
}
];
private dataManager: DataManager = new DataManager(this.localData);
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
}Remote Data Binding
Connect to remote services using DataManager with appropriate adaptors.
Using ODataV4 Adaptor
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[readonly]="readonly"
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public readonly: boolean = true;
public selectedDate: Date = new Date(2020, 9, 20);
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor(),
crossDomain: true
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
}Using Web API Adaptor
import { DataManager, WebApiAdaptor } from '@syncfusion/ej2-data';
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new WebApiAdaptor(),
crossDomain: true
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
}Filter Events with Built-in Query
Enable server-side filtering using includeFiltersInQuery:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[readonly]="readonly"
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public readonly: boolean = true;
public selectedDate: Date = new Date(1996, 6, 9);
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor(),
crossDomain: true
});
public eventSettings: EventSettingsModel = {
includeFiltersInQuery: true, // Enable server-side filtering
dataSource: this.dataManager,
fields: {
id: 'Id',
subject: { name: 'ShipName' },
location: { name: 'ShipCountry' },
description: { name: 'ShipAddress' },
startTime: { name: 'OrderDate' },
endTime: { name: 'RequiredDate' },
recurrenceRule: { name: 'ShipRegion' }
}
};
}Benefits:
- Reduces data transfer to client
- Improves performance with large datasets
- Filters by start date, end date, and recurrence rule
Considerations:
- May result in longer query strings
- Check server limitations on query string length
Using URL Adaptor for CRUD
import { DataManager, UrlAdaptor } from '@syncfusion/ej2-data';
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
private dataManager: DataManager = new DataManager({
url: 'Home/GetData', // GET endpoint
crudUrl: 'Home/UpdateData', // CRUD endpoint
adaptor: new UrlAdaptor()
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
}Loading Data via AJAX
import { Component, OnInit, ViewChild } from '@angular/core';
import { Ajax } from '@syncfusion/ej2-base';
import { ScheduleComponent, ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#scheduleObj
width='100%'
height='550px'
[selectedDate]='selectedDate'
(created)="onCreate()">
</ejs-schedule>
`
})
export class AppComponent implements OnInit {
@ViewChild('scheduleObj') public scheduleObj!: ScheduleComponent;
public selectedDate: Date = new Date(2024, 0, 15);
ngOnInit(): void {}
onCreate() {
const ajax = new Ajax(
'url',
'GET'
);
ajax.send();
ajax.onSuccess = (data: string) => {
this.scheduleObj.eventSettings.dataSource = JSON.parse(data);
};
}
}CRUD Operations
The Scheduler supports full CRUD (Create, Read, Update, Delete) operations on events.
Adding Events
Method 1: Using Editor Window
Double-click on a cell to open the default editor window with fields:
- Subject
- Location
- Start Time / End Time
- All-Day checkbox
- Timezone
- Description
- Recurrence settings
Single-click on a cell for quick popup (Subject only).
Select multiple cells and press Enter for quick popup with time range.
Method 2: Using addEvent() Method
Add events programmatically:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<button (click)="onAddClick()">Add Events</button>
<ejs-schedule
#scheduleObj
width='100%'
height='520px'
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('scheduleObj') public scheduleObj!: ScheduleComponent;
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [
{
Id: 1,
Subject: 'Existing Event',
StartTime: new Date(2024, 0, 11, 9, 0),
EndTime: new Date(2024, 0, 11, 10, 0)
}
]
};
onAddClick(): void {
const data: Object[] = [
{
Id: 2,
Subject: 'Conference',
StartTime: new Date(2024, 0, 12, 9, 0),
EndTime: new Date(2024, 0, 12, 10, 0),
IsAllDay: false
},
{
Id: 3,
Subject: 'Meeting',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 11, 30),
IsAllDay: false
}
];
this.scheduleObj.addEvent(data);
}
}Add Single Event:
this.scheduleObj.addEvent({
Id: 4,
Subject: 'New Meeting',
StartTime: new Date(2024, 0, 16, 14, 0),
EndTime: new Date(2024, 0, 16, 15, 0)
});Updating Events
Method 1: Using Editor Window
Click on an event to open quick popup, then click Edit to open full editor.
Or double-click an event to directly open the editor.
Method 2: Using saveEvent() Method
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
template: `
<button (click)="onUpdateClick()">Update Event</button>
<ejs-schedule #scheduleObj></ejs-schedule>
`
})
export class AppComponent {
@ViewChild('scheduleObj') public scheduleObj!: ScheduleComponent;
onUpdateClick(): void {
const event = {
Id: 1,
Subject: 'Updated Meeting Title',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 12, 0), // Extended duration
Location: 'Updated Location'
};
this.scheduleObj.saveEvent(event);
}
}Update Multiple Events:
this.scheduleObj.saveEvent([event1, event2, event3]);Deleting Events
Method 1: Using Editor/Quick Popup
Click event → Quick Popup → Click Delete button.
Or open editor and click Delete button.
Method 2: Using deleteEvent() Method
Delete by ID:
this.scheduleObj.deleteEvent(1); // Delete event with Id = 1Delete by Event Object:
const event = {
Id: 2,
Subject: 'Meeting to Delete',
StartTime: new Date(2024, 0, 15, 10, 0),
EndTime: new Date(2024, 0, 15, 11, 0)
};
this.scheduleObj.deleteEvent(event);Delete Multiple Events:
this.scheduleObj.deleteEvent([1, 2, 3]); // Delete by IDs
// Or by objects
this.scheduleObj.deleteEvent([event1, event2, event3]);Delete Recurring Event Occurrence:
// Delete single occurrence from recurring series
this.scheduleObj.deleteEvent(eventObject, 'DeleteOccurrence');
// Delete all following occurrences
this.scheduleObj.deleteEvent(eventObject, 'DeleteFollowingEvents');
// Delete entire series
this.scheduleObj.deleteEvent(eventObject, 'DeleteSeries');Server-Side CRUD Actions
Server-Side Insert
if (param.action == "insert" || (param.action == "batch" && param.added != null))
{
var value = (param.action == "insert") ? param.value : param.added[0];
int intMax = db.ScheduleEventDatas.ToList().Count > 0
? db.ScheduleEventDatas.ToList().Max(p => p.Id) : 1;
DateTime startTime = Convert.ToDateTime(value.StartTime);
DateTime endTime = Convert.ToDateTime(value.EndTime);
ScheduleEventData appointment = new ScheduleEventData()
{
Id = intMax + 1,
StartTime = startTime.ToLocalTime(),
EndTime = endTime.ToLocalTime(),
Subject = value.Subject,
IsAllDay = value.IsAllDay,
StartTimezone = value.StartTimezone,
EndTimezone = value.EndTimezone,
RecurrenceRule = value.RecurrenceRule,
RecurrenceID = value.RecurrenceID,
RecurrenceException = value.RecurrenceException
};
db.ScheduleEventDatas.InsertOnSubmit(appointment);
db.SubmitChanges();
}Server-Side Update
if (param.action == "update" || (param.action == "batch" && param.changed != null))
{
var value = (param.action == "update") ? param.value : param.changed[0];
var filterData = db.ScheduleEventDatas.Where(c => c.Id == Convert.ToInt32(value.Id));
if (filterData.Count() > 0)
{
DateTime startTime = Convert.ToDateTime(value.StartTime);
DateTime endTime = Convert.ToDateTime(value.EndTime);
ScheduleEventData appointment = db.ScheduleEventDatas.Single(A => A.Id == Convert.ToInt32(value.Id));
appointment.StartTime = startTime.ToLocalTime();
appointment.EndTime = endTime.ToLocalTime();
appointment.StartTimezone = value.StartTimezone;
appointment.EndTimezone = value.EndTimezone;
appointment.Subject = value.Subject;
appointment.IsAllDay = value.IsAllDay;
appointment.RecurrenceRule = value.RecurrenceRule;
appointment.RecurrenceID = value.RecurrenceID;
appointment.RecurrenceException = value.RecurrenceException;
}
db.SubmitChanges();
}Server-Side Delete
if (param.action == "remove" || (param.action == "batch" && param.deleted != null))
{
if (param.action == "remove")
{
int key = Convert.ToInt32(param.key);
ScheduleEventData appointment = db.ScheduleEventDatas.Where(c => c.Id == key).FirstOrDefault();
if (appointment != null) db.ScheduleEventDatas.DeleteOnSubmit(appointment);
}
else
{
foreach (var apps in param.deleted)
{
ScheduleEventData appointment = db.ScheduleEventDatas.Where(c => c.Id == apps.Id).FirstOrDefault();
if (appointment != null) db.ScheduleEventDatas.DeleteOnSubmit(appointment);
}
}
db.SubmitChanges();
}Custom Adaptor
Create custom adaptors by extending built-in adaptors:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DataManager, ODataV4Adaptor } from '@syncfusion/ej2-data';
class CustomAdaptor extends ODataV4Adaptor {
override processResponse(): Object {
let i: number = 0;
// Call base class processResponse
let original: any = super.processResponse.apply(this, arguments as any);
// Add custom field
original.forEach((item: any) => item['EventID'] = ++i);
return original;
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[readonly]="readonly"
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public readonly: boolean = true;
public selectedDate: Date = new Date(1996, 6, 9);
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new CustomAdaptor()
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager,
fields: {
id: 'Id',
subject: { name: 'ShipName' },
location: { name: 'ShipCountry' },
description: { name: 'ShipAddress' },
startTime: { name: 'OrderDate' },
endTime: { name: 'RequiredDate' },
recurrenceRule: { name: 'ShipRegion' }
}
};
}Additional Parameters
Pass custom parameters to the server using Query.addParams():
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DataManager, ODataV4Adaptor, Query } from '@syncfusion/ej2-data';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[readonly]="readonly"
[selectedDate]="selectedDate"
[eventSettings]="eventSettings">
</ejs-schedule>
`
})
export class AppComponent {
public readonly: boolean = true;
public selectedDate: Date = new Date(2024, 0, 15);
private dataManager: DataManager = new DataManager({
url: 'url',
adaptor: new ODataV4Adaptor()
});
private dataQuery: Query = new Query()
.from('Events')
.addParams('readOnly', 'true')
.addParams('userId', '12345');
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager,
query: this.dataQuery
};
}Note: Parameters added via query are sent with every data request.
Error Handling
Handle server-side errors using the actionFailure event:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DataManager } from '@syncfusion/ej2-data';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<span style="color: #FF0000">{{errorMessage}}</span>
<ejs-schedule
#scheduleObj
width='100%'
height='530px'
[readonly]="readonly"
[selectedDate]="selectedDate"
[eventSettings]="eventSettings"
(actionFailure)="onActionFailure($event)">
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('scheduleObj') public scheduleObj!: ScheduleComponent;
public readonly: boolean = false;
public selectedDate: Date = new Date(2024, 0, 15);
public errorMessage: string = '';
private dataManager: DataManager = new DataManager({
url: 'http://invalid-url.com/api/schedule' // Invalid URL for demo
});
public eventSettings: EventSettingsModel = {
dataSource: this.dataManager
};
onActionFailure(eventData: any): void {
this.errorMessage = `Server exception: ${eventData.error.status} ${eventData.error.statusText}`;
console.error('Action failed:', eventData);
}
}actionFailure triggers when:
- Server returns HTTP error (404, 500, etc.)
- Network failure occurs
- Exception happens during CRUD operations
Best Practices
1. Use DataManager for Remote Data: Provides unified interface for CRUD operations 2. Enable Server Filtering: Use includeFiltersInQuery for large datasets 3. Handle Errors Gracefully: Always implement actionFailure event handler 4. Validate Data: Validate on both client and server side 5. Use Appropriate Adaptor: Choose adaptor matching your server API (ODataV4, WebApi, UrlAdaptor) 6. Batch Operations: Use addEvent([]), saveEvent([]), deleteEvent([]) for multiple events 7. Custom Parameters: Use Query.addParams() for filtering or authentication tokens
Common Issues
Events not loading
- Solution: Check network tab for API errors; verify URL and adaptor configuration
CRUD operations not working
- Solution: Ensure
crudUrlis set for UrlAdaptor; verify server endpoints accept correct HTTP methods
Events not updating in UI
- Solution: Ensure server returns updated data; check
dataSourceis properly bound
Cross-origin errors
- Solution: Set
crossDomain: truein DataManager; configure CORS on server
Custom fields not saving
- Solution: Map custom fields using
fieldsproperty; ensure server accepts custom fields
Editor and Popup Customization
Table of Contents
- Overview
- Default Event Editor
- Opening the Editor
- Customize Editor Header and Button Text
- Change Field Labels
- Field Validation
- Add Additional Fields
- Customize Event Duration
- Prevent Editor Popups
- Prevent Editor on Cell Double-Click
- Customize Timezone Collection
- Custom Editor Template
- Complete Editor Template
- Save Custom Template Data Without e-field Class
- Custom Header and Footer Templates
- Quick Info Popups
- Disable Quick Info
- Customize Quick Info Templates
- More Events Indicator
- Disable More Indicator Popup
- Customize More Indicator Popup
- Navigate to Day View on More Click
- Best Practices
- Common Scenarios
- Read-Only Scheduler
- Minimal Editor (Subject Only)
- Custom Event Categories
- Approval Workflow
- Required Attendees
- Priority/Urgency Fields
Overview
The Scheduler provides built-in editors and popups for creating and managing events. Customization options include:
- Event Editor: Detailed window for creating/editing appointments
- Quick Info Popup: Quick view/edit on cell or event click
- More Indicator Popup: Shows additional events when cells overflow
- Custom Templates: Complete control over editor appearance and behavior
Default Event Editor
Opening the Editor
The editor window opens in different modes:
- Add Mode: Double-click an empty cell
- Edit Mode: Double-click an existing event
- Mobile: Single tap event → Edit icon, or tap cell twice for add mode
Prevent Editor Opening:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[readonly]='true'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}Customize Editor Header and Button Text
Change header title and button labels using localization:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { L10n } from '@syncfusion/ej2-base';
L10n.load({
'en-US': {
'schedule': {
'saveButton': 'Add',
'cancelButton': 'Close',
'deleteButton': 'Remove',
'newEvent': 'Add Event'
}
}
});
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}Change Field Labels
Customize default field labels like "Subject", "Location":
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [],
fields: {
id: 'Id',
subject: { name: 'Subject', title: 'Event Name' },
location: { name: 'Location', title: 'Event Location' },
description: { name: 'Description', title: 'Event Description' },
startTime: { name: 'StartTime', title: 'Start Duration' },
endTime: { name: 'EndTime', title: 'End Duration' }
}
};
}Field Validation
Add client-side validation rules to editor fields:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
minValidation = (args: { [key: string]: string }) => {
return args['value'].length >= 5;
};
public eventSettings: EventSettingsModel = {
dataSource: [],
fields: {
id: 'Id',
subject: {
name: 'Subject',
validation: { required: true }
},
location: {
name: 'Location',
validation: { required: true }
},
description: {
name: 'Description',
validation: {
required: true,
minLength: [this.minValidation, 'Need at least 5 letters']
}
},
startTime: {
name: 'StartTime',
validation: { required: true }
},
endTime: {
name: 'EndTime',
validation: { required: true }
}
}
};
}Available Validation Rules:
required: trueminLength: [validator, message]maxLength: numberemail: trueregex: [pattern, message]
Add Additional Fields
Add custom fields to the default editor using popupOpen event:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DropDownList } from '@syncfusion/ej2-dropdowns';
import { createElement } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
(popupOpen)='onPopupOpen($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onPopupOpen(args: PopupOpenEventArgs): void {
if (args.type === 'Editor') {
// Create custom field only once
if (!args.element.querySelector('.custom-field-row')) {
let row: HTMLElement = createElement('div', { className: 'custom-field-row' });
let formElement: HTMLElement = args.element.querySelector('.e-schedule-form') as HTMLElement;
formElement.firstChild?.insertBefore(row, args.element.querySelector('.e-title-location-row'));
let container: HTMLElement = createElement('div', { className: 'custom-field-container' });
let inputEle: HTMLInputElement = createElement('input', {
className: 'e-field',
attrs: { name: 'EventType' }
}) as HTMLInputElement;
container.appendChild(inputEle);
row.appendChild(container);
let dropDownList: DropDownList = new DropDownList({
dataSource: [
{ text: 'Public Event', value: 'public-event' },
{ text: 'Maintenance', value: 'maintenance' },
{ text: 'Commercial Event', value: 'commercial-event' },
{ text: 'Family Event', value: 'family-event' }
],
fields: { text: 'text', value: 'value' },
value: (args.data as any)['EventType'] as string,
floatLabelType: 'Always',
placeholder: 'Event Type'
});
dropDownList.appendTo(inputEle);
}
}
}
}Key Points:
- Add
e-fieldclass for automatic data processing - Use
popupOpenevent to inject custom elements - Check if element exists to avoid duplicates
Customize Event Duration
Change default event duration (default: 60 minutes from timeScale.interval):
onPopupOpen(args: PopupOpenEventArgs): void {
if (args.type === 'Editor') {
args.duration = 30; // 30 minutes
}
}Prevent Editor Popups
Prevent specific popup types from opening:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[eventSettings]='eventSettings'
(popupOpen)='onPopupOpen($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onPopupOpen(args: PopupOpenEventArgs): void {
// Prevent specific popups
if (args.type === 'Editor' || args.type === 'QuickInfo') {
args.cancel = true;
}
}
}Popup Types:
'Editor': Detailed editor window'QuickInfo': Quick popup on cell click'EditEventInfo': Quick popup on event click'ViewEventInfo': Quick popup in responsive mode'EventContainer': More event indicator popup'RecurrenceAlert': Edit recurrence alert'DeleteAlert': Delete confirmation'ValidationAlert': Validation alert'RecurrenceValidationAlert': Recurrence validation alert
Prevent Editor on Cell Double-Click
Block editor opening specifically on cell double-click:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, CellClickEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[eventSettings]='eventSettings'
(cellDoubleClick)='onCellDoubleClick($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onCellDoubleClick(args: CellClickEventArgs): void {
args.cancel = true;
// Implement custom behavior here
}
}Customize Timezone Collection
Show limited timezone options in the editor:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[timezoneDataSource]='timezoneDataSource'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public timezoneDataSource: { Value: string, Text: string }[] = [
{ Value: 'Pacific/Honolulu', Text: 'Hawaii Time' },
{ Value: 'America/Los_Angeles', Text: 'Pacific Time' },
{ Value: 'America/Chicago', Text: 'Central Time' },
{ Value: 'America/New_York', Text: 'Eastern Time' },
{ Value: 'UTC', Text: 'UTC' },
{ Value: 'Europe/London', Text: 'London' },
{ Value: 'Asia/Kolkata', Text: 'India' }
];
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
}Custom Editor Template
Complete Editor Template
Replace the default editor with a fully customized template:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { DateTimePickerModule } from '@syncfusion/ej2-angular-calendars';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
import { ChangeEventArgs } from '@syncfusion/ej2-calendars';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule, DateTimePickerModule, DropDownListModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#schedule
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[showQuickInfo]='false'>
<ng-template #editorTemplate let-data>
<table class="custom-event-editor" width="100%" cellpadding="5">
<tbody>
<tr>
<td class="e-textlabel">Summary</td>
<td colspan="4">
<input
id="Subject"
class="e-field e-input"
type="text"
name="Subject"
style="width: 100%" />
</td>
</tr>
<tr>
<td class="e-textlabel">Status</td>
<td colspan="4">
<ejs-dropdownlist
id='EventType'
class="e-field"
data-name="EventType"
placeholder='Choose Status'
[dataSource]='statusData'
value="{{data.EventType}}">
</ejs-dropdownlist>
</td>
</tr>
<tr>
<td class="e-textlabel">From</td>
<td colspan="4">
<ejs-datetimepicker
id="StartTime"
class="e-field"
data-name="StartTime"
format="M/dd/yy h:mm a"
(change)="onDateChange($event)"
[value]="startDateParser(data.startTime || data.StartTime)">
</ejs-datetimepicker>
</td>
</tr>
<tr>
<td class="e-textlabel">To</td>
<td colspan="4">
<ejs-datetimepicker
id="EndTime"
class="e-field"
data-name="EndTime"
format="M/dd/yy h:mm a"
(change)="onDateChange($event)"
[value]="endDateParser(data.endTime || data.EndTime)">
</ejs-datetimepicker>
</td>
</tr>
<tr>
<td class="e-textlabel">Reason</td>
<td colspan="4">
<textarea
id="Description"
class="e-field e-input"
name="Description"
rows="3"
cols="50"
value="{{data.Description}}"
style="width: 100%; height: 60px !important; resize: vertical">
</textarea>
</td>
</tr>
</tbody>
</table>
</ng-template>
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
public selectedDate: Date = new Date(2024, 0, 15);
public startDate!: Date;
public endDate!: Date;
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
public statusData: Object[] = ['New', 'Requested', 'Confirmed'];
startDateParser(data: string) {
if (isNullOrUndefined(this.startDate) && !isNullOrUndefined(data)) {
return new Date(data);
} else if (!isNullOrUndefined(this.startDate)) {
return new Date(this.startDate);
}
return new Date();
}
endDateParser(data: string) {
if (isNullOrUndefined(this.endDate) && !isNullOrUndefined(data)) {
return new Date(data);
} else if (!isNullOrUndefined(this.endDate)) {
return new Date(this.endDate);
}
return new Date();
}
onDateChange(args: ChangeEventArgs): void {
if (!isNullOrUndefined(args.event)) {
if (args.element.id === 'StartTime') {
this.startDate = args.value as Date;
} else if (args.element.id === 'EndTime') {
this.endDate = args.value as Date;
}
}
}
}Template Requirements:
- Add
e-fieldclass to all form elements for automatic processing - Use
data-nameattribute to map fields - Supported components: DropDownList, DateTimePicker, MultiSelect, DatePicker, CheckBox, TextBox
Save Custom Template Data Without e-field Class
Handle data manually using popupClose event:
import { Component, ViewChild, ElementRef } from '@angular/core';
import { ScheduleModule, PopupCloseEventArgs } from '@syncfusion/ej2-angular-schedule';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
template: `
<ejs-schedule
width='100%'
height='550px'
[eventSettings]='eventSettings'
(popupClose)='onPopupClose($event)'>
<ng-template #editorTemplate let-data>
<input
id="Subject"
type="text"
#subject
value="{{data.Subject}}" />
</ng-template>
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('subject') public subject!: ElementRef<HTMLInputElement>;
public eventSettings = { dataSource: [] };
onPopupClose(args: PopupCloseEventArgs): void {
if (args.type === 'Editor' && !isNullOrUndefined(args.data)) {
// Save only on 'save' and 'delete', not 'close' or 'cancel'
if (this.subject.nativeElement) {
(args.data as any)['Subject'] = this.subject.nativeElement.value;
}
}
}
}Custom Header and Footer Templates
Customize editor window header and footer:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule, CommonModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#schedule
width='100%'
height='550px'
[eventSettings]='eventSettings'
(popupOpen)='onPopupOpen($event)'>
<ng-template #editorHeaderTemplate let-data>
<div *ngIf="data.Subject; else createNewEvent">
{{ data.Subject }}
</div>
<ng-template #createNewEvent>
Create New Event
</ng-template>
</ng-template>
<ng-template #editorFooterTemplate>
<div id="verify">
<input type="checkbox" id="check-box" value="unchecked">
<label id="text">Verified</label>
</div>
<div id="right-button">
<button id="Save" class="e-control e-btn e-primary" disabled>Save</button>
<button id="Cancel" class="e-control e-btn e-primary">Cancel</button>
</div>
</ng-template>
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
public eventSettings = { dataSource: [] };
onPopupOpen(args: PopupOpenEventArgs): void {
if (args.type === 'Editor') {
const saveButton = args.element.querySelector('#Save') as HTMLElement;
const cancelButton = args.element.querySelector('#Cancel') as HTMLElement;
const checkBox = args.element.querySelector('#check-box') as HTMLInputElement;
checkBox.onchange = () => {
if (checkBox.checked) {
saveButton.removeAttribute('disabled');
} else {
saveButton.setAttribute('disabled', '');
}
};
saveButton.onclick = () => {
// Save logic
this.scheduleObj.closeEditor();
};
cancelButton.onclick = () => {
this.scheduleObj.closeEditor();
};
}
}
}Quick Info Popups
Disable Quick Info
Hide all quick popups:
<ejs-schedule
width='100%'
height='550px'
[showQuickInfo]='false'
[eventSettings]='eventSettings'>
</ejs-schedule>Customize Quick Info Templates
Use quickInfoTemplates to customize quick popup content:
import { Component, ViewChild } from '@angular/core';
import { ScheduleComponent, ScheduleModule, EventSettingsModel, CurrentAction } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#schedule
width='100%'
height='550px'
[eventSettings]='eventSettings'>
<ng-template #quickInfoTemplatesHeader let-data>
<div class="quick-info-header">
<span class="e-subject">{{data.Subject}}</span>
</div>
</ng-template>
<ng-template #quickInfoTemplatesContent let-data>
<div class="quick-info-content">
<div><strong>Time:</strong> {{formatTime(data.StartTime)}} - {{formatTime(data.EndTime)}}</div>
<div *ngIf="data.Location"><strong>Location:</strong> {{data.Location}}</div>
<div *ngIf="data.Description"><strong>Description:</strong> {{data.Description}}</div>
</div>
</ng-template>
<ng-template #quickInfoTemplatesFooter let-data>
<div class="quick-info-footer">
<button class="e-btn e-flat" (click)="onEditClick()">Edit</button>
<button class="e-btn e-flat" (click)="onDeleteClick()">Delete</button>
<button class="e-btn e-flat" (click)="onCloseClick()">Close</button>
</div>
</ng-template>
</ejs-schedule>
`
})
export class AppComponent {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
private selectionTarget: Element | null = null;
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
formatTime(date: Date): string {
return new Date(date).toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit'
});
}
onEditClick(): void {
this.onCloseClick();
if (this.selectionTarget) {
const eventData = this.scheduleObj.getEventDetails(this.selectionTarget);
this.scheduleObj.openEditor(eventData, 'Save');
}
}
onDeleteClick(): void {
this.onCloseClick();
if (this.selectionTarget) {
const eventData = this.scheduleObj.getEventDetails(this.selectionTarget);
this.scheduleObj.deleteEvent(eventData, 'Delete');
}
}
onCloseClick(): void {
this.scheduleObj.quickPopup.quickPopupHide();
}
}Template Properties:
quickInfoTemplatesHeader: Custom header contentquickInfoTemplatesContent: Custom body contentquickInfoTemplatesFooter: Custom footer buttons
More Events Indicator
Disable More Indicator Popup
Prevent the "+N more" popup from opening:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
currentView='Month'
[eventSettings]='eventSettings'
(popupOpen)='onPopupOpen($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data with many events
};
onPopupOpen(args: PopupOpenEventArgs): void {
if (args.type === 'EventContainer') {
args.cancel = true;
}
}
}Customize More Indicator Popup
Modify the popup header to show event count:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, PopupOpenEventArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
import { Internationalization } from '@syncfusion/ej2-base';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
width='100%'
height='550px'
currentView='Month'
[eventSettings]='eventSettings'
(popupOpen)='onPopupOpen($event)'>
</ejs-schedule>
`
})
export class AppComponent {
private instance: Internationalization = new Internationalization();
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onPopupOpen(args: PopupOpenEventArgs): void {
if (args.type === 'EventContainer') {
const dateStr = this.instance.formatDate((args.data as any).date, { skeleton: 'MMMEd' });
const eventCount = (args.data as any).event.length;
(args.element.querySelector('.e-header-date') as HTMLElement).innerText = dateStr;
(args.element.querySelector('.e-header-day') as HTMLElement).innerText = `Event count: ${eventCount}`;
}
}
}Navigate to Day View on More Click
Replace popup with navigation:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel, MoreEventsClickArgs } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
#schedule
width='100%'
height='550px'
currentView='Month'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
(moreEventsClick)='onMoreEventsClick($event)'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = {
dataSource: [] // Your event data
};
onMoreEventsClick(args: MoreEventsClickArgs): void {
args.cancel = true; // Prevent popup
args.isPopupOpen = false;
// Navigate to Day view for the clicked date
(args.event.currentTarget as HTMLElement).classList.add('e-navigate');
}
}Best Practices
1. e-field Class: Always add for automatic data processing in templates 2. Validation: Apply validation rules to prevent invalid data 3. PopupOpen Event: Use for dynamic customization based on context 4. PopupClose Event: Handle manual data extraction when not using e-field 5. Localization: Use L10n for multi-language support 6. Timezone: Provide limited, relevant timezone options 7. Quick Info: Disable if using custom editors for consistency 8. More Indicator: Customize or disable based on UX requirements 9. Mobile: Test quick info behavior on touch devices 10. Performance: Avoid heavy processing in popupOpen event
Common Scenarios
Read-Only Scheduler
[readonly]='true'Minimal Editor (Subject Only)
fields: {
subject: { name: 'Subject', validation: { required: true } }
}Custom Event Categories
Add dropdown with predefined categories using additional fields.
Approval Workflow
Use custom footer template with "Approve" and "Reject" buttons.
Required Attendees
Add MultiSelect for attendees with validation.
Priority/Urgency Fields
Add DropDownList or Rating component for priority levels.
State Persistence
Table of Contents
Save and restore the Scheduler state (current view, selected date, scroll position) across sessions using browser storage.
Enable State Persistence
Set enablePersistence to automatically save Scheduler state to localStorage:
import { Component } from '@angular/core';
import { ScheduleModule, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
import { DayService, WeekService, MonthService } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
standalone: true,
imports: [ScheduleModule],
providers: [DayService, WeekService, MonthService],
template: `
<ejs-schedule
id='schedule'
width='100%'
height='550px'
[selectedDate]='selectedDate'
[eventSettings]='eventSettings'
[enablePersistence]='true'>
</ejs-schedule>
`
})
export class AppComponent {
public selectedDate: Date = new Date(2024, 0, 15);
public eventSettings: EventSettingsModel = { dataSource: [] };
}Persisted Properties:
currentView- Active view (Day/Week/Month)selectedDate- Currently displayed datescrollLeft- Horizontal scroll position (Timeline views)scrollTop- Vertical scroll position
Storage Key: Component id + _persistence (e.g., schedule_persistence)
Requirements
- Component ID: Must set
idproperty for unique storage key - Browser Support: localStorage must be available
- Same Origin: Works only within same domain/protocol
Clear Persisted State
Remove saved state programmatically:
import { Component, ViewChild, OnInit } from '@angular/core';
import { ScheduleComponent } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
template: `
<button (click)="clearState()">Clear Saved State</button>
<ejs-schedule
#schedule
id='schedule'
[enablePersistence]='true'>
</ejs-schedule>
`
})
export class AppComponent implements OnInit {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
ngOnInit() {
// Clear state on component init if needed
// localStorage.removeItem('schedule_persistence');
}
clearState(): void {
localStorage.removeItem('schedule_persistence');
location.reload(); // Reload to apply default state
}
}Custom State Management
Implement custom persistence logic:
import { Component, ViewChild, OnDestroy } from '@angular/core';
import { ScheduleComponent, EventSettingsModel } from '@syncfusion/ej2-angular-schedule';
@Component({
selector: 'app-root',
template: `
<ejs-schedule
#schedule
id='schedule'
[(currentView)]='currentView'
[(selectedDate)]='selectedDate'
[eventSettings]='eventSettings'>
</ejs-schedule>
`
})
export class AppComponent implements OnDestroy {
@ViewChild('schedule') public scheduleObj!: ScheduleComponent;
public currentView: any = this.loadState('currentView', 'Week');
public selectedDate: Date = new Date(this.loadState('selectedDate', new Date().toISOString()));
public eventSettings: EventSettingsModel = { dataSource: [] };
loadState(key: string, defaultValue: any): any {
const saved = sessionStorage.getItem(`schedule_${key}`);
return saved ? JSON.parse(saved) : defaultValue;
}
ngOnDestroy() {
// Save state before component destruction
sessionStorage.setItem('schedule_currentView', JSON.stringify(this.currentView));
sessionStorage.setItem('schedule_selectedDate', JSON.stringify(this.selectedDate));
}
}Best Practices
1. Unique IDs: Ensure unique component IDs when using multiple Schedulers 2. Privacy: Use sessionStorage for sensitive applications 3. Versioning: Include version number in storage key for breaking changes 4. Migration: Provide migration logic when persisted structure changes 5. Validation: Validate loaded state before applying 6. Fallback: Always provide default values if persistence fails 7. Testing: Test with disabled localStorage (private browsing) 8. Storage Limits: localStorage has ~5-10MB limit per domain 9. Clear on Logout: Remove persisted state on user logout 10. Documentation: Inform users about state persistence behavior