---
title: "Angular Event calendar Add, Edit and Delete Events Example | Mobiscroll"
component: "Event calendar"
framework: "Angular"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/angular/eventcalendar/create-read-update-delete-CRUD"
---

# Add, Edit and Delete Events - Angular Event calendar demo

Add full event management to a Angular event calendar — create events by drag or click, edit them in a custom dialog, and delete with undo. For Angular 22 and Ionic 8.

- [Live demo](https://demo.mobiscroll.com/angular/eventcalendar/create-read-update-delete-CRUD)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-angular/tree/main/src/demos/eventcalendar/calendar-view/create-read-update-delete-CRUD)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/eventcalendar/create-read-update-delete-CRUD.md), [jQuery](https://demo.mobiscroll.com/jquery/eventcalendar/create-read-update-delete-CRUD.md), [React](https://demo.mobiscroll.com/react/eventcalendar/create-read-update-delete-CRUD.md), [Vue](https://demo.mobiscroll.com/vue/eventcalendar/create-read-update-delete-CRUD.md)

## Description

**Create new events**

Drag to create and click to create is enabled. Events can be created by dragging or with double clicks. As soon as the initial position is confirmed, a temporary event is created and a custom edit dialog is shown for refinement. On cancel the temporary event will be removed and on confirmation the event will stay in the calendar.

**Edit existing events**

Drag to resize and drag to move is enabled. Events can be reordered and resized. In addition to that, clicking on the event will open a custom dialog that enables editing the various properties.

**Delete events**

Delete can be implemented inside the edit dialog with a button. It's just a matter of removing it from the data object. If a dialog is not shown on click, focused events can be deleted with the *backspace* and *delete* keys.

**Interested in adding recurrence configuration?** [Check out the next example →](https://demo.mobiscroll.com/angular/eventcalendar/recurring-event-add-edit-dialog)

## What this demo shows

- A full month event calendar with multiple events and the standard header controls for month and year navigation, previous and next arrows, and a Today button.
- **Event creation** Users can create events by clicking and dragging across calendar cells or by double-clicking a day cell.
- **New event dialog** Creating an event opens a dialog with fields for title, description, all-day, start and end date selection, travel time, event color, and busy or free status.
- **Date selection** The start and end inputs open a date picker calendar with range selection.
- **Cancel behavior** Clicking outside the dialog or pressing Cancel closes it and removes the temporary event from the calendar.
- **Event editing** Clicking or tapping an existing event opens the same dialog in edit mode with the current values prefilled.
- **Editable properties** In edit mode, the event details and event color can be changed before saving.
- **Save and close behavior** Clicking outside the dialog or pressing Cancel closes the editor without applying changes, while Save updates the event.
- **Delete flow** The edit dialog includes a Delete event action that removes the event from the calendar.
- **Undo feedback** After deletion, a centered bottom toast appears with an Undo action so the deleted event can be restored.
- **Responsive behavior** The dialog adapts to screen size and device type, using touch-optimized controls such as scrollers on smaller or touch devices.
- **Mobile presentation** On smaller screens, the dialog is shown as a sheet instead of a popover.
- **Day cell states** Hovering a day cell highlights the day number with a gray background, while clicking the empty part of the cell selects the day and highlights the day number with a blue background.

## Best for

- **Monthly event management** Managing create, read, update, and delete workflows in a month-based calendar view.
- **General scheduling** Applications where users need to add, edit, and remove events directly from the calendar.
- **Mixed event types** Calendars that need to handle single-day, multi-day, all-day, and timed events in the same interface.
- **Responsive scheduling UIs** Products that need the same event management flow to work well on desktop and touch devices.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-themeVariant) - option
- [`labels`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-labels) - option
- [`colors`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-colors) - option
- [`cssClass`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-cssClass) - option
- [`navigateToEvent`](https://mobiscroll.com/docs/angular/eventcalendar/api#method-navigateToEvent) - method
- [`display`](https://mobiscroll.com/docs/angular/popup/api#opt-display) - option
- [`width`](https://mobiscroll.com/docs/angular/popup/api#opt-width) - option
- [`fullScreen`](https://mobiscroll.com/docs/angular/popup/api#opt-fullScreen) - option
- [`touchUi`](https://mobiscroll.com/docs/angular/popup/api#opt-touchUi) - option
- [`cssClass`](https://mobiscroll.com/docs/angular/popup/api#opt-cssClass) - option
- [`buttons`](https://mobiscroll.com/docs/angular/popup/api#opt-buttons) - option
- [`data`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-data) - option
- [`clickToCreate`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-clickToCreate) - option
- [`dragToCreate`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-dragToResize) - option
- [`view`](https://mobiscroll.com/docs/angular/eventcalendar/api#opt-view) - option
- [`onEventClick`](https://mobiscroll.com/docs/angular/eventcalendar/api#event-onEventClick) - event
- [`onEventCreated`](https://mobiscroll.com/docs/angular/eventcalendar/api#event-onEventCreated) - event
- [`onEventDeleted`](https://mobiscroll.com/docs/angular/eventcalendar/api#event-onEventDeleted) - event
- [`scrollLock`](https://mobiscroll.com/docs/angular/popup/api#opt-scrollLock) - option
- [`headerText`](https://mobiscroll.com/docs/angular/popup/api#opt-headerText) - option
- [`anchor`](https://mobiscroll.com/docs/angular/popup/api#opt-anchor) - option
- [`responsive`](https://mobiscroll.com/docs/angular/popup/api#opt-responsive) - option
- [`onClose`](https://mobiscroll.com/docs/angular/popup/api#event-onClose) - event
- [`select`](https://mobiscroll.com/docs/angular/datepicker/api#opt-select) - option
- [`display`](https://mobiscroll.com/docs/angular/datepicker/api#opt-display) - option
- [`touchUi`](https://mobiscroll.com/docs/angular/datepicker/api#opt-touchUi) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/angular/datepicker/api#opt-showRangeLabels) - option
- [`controls`](https://mobiscroll.com/docs/angular/datepicker/api#opt-controls) - option
- [`responsive`](https://mobiscroll.com/docs/angular/datepicker/api#opt-responsive) - option
- [`startInput`](https://mobiscroll.com/docs/angular/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/angular/datepicker/api#opt-endInput) - option
- [`showArrow`](https://mobiscroll.com/docs/angular/popup/api#opt-showArrow) - option
- [`showOverlay`](https://mobiscroll.com/docs/angular/popup/api#opt-showOverlay) - option

## Code

### Component

```ts
import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
  MbscCalendarEvent,
  MbscDateType,
  MbscEventcalendar,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventCreatedEvent,
  MbscModule,
  MbscPopup,
  MbscPopupButton,
  MbscPopupOptions,
  MbscResponsiveOptions,
  Notifications,
  setOptions,
} from '@mobiscroll/angular';

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

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

  @ViewChild('calendar', { static: false })
  calendar!: MbscEventcalendar;

  @ViewChild('addEditPopup', { static: false })
  addEditPopup!: MbscPopup;

  @ViewChild('colorPicker', { static: false })
  colorPicker!: MbscPopup;

  eventId: string | number | undefined;
  eventTitle: string | undefined;
  eventDescription = '';
  eventAllDay = false;
  eventDates: MbscDateType[] = [];
  eventBuffer = 0;
  eventColor = '';
  eventStatus = false;

  selectedColor = '';
  statusValue = 'busy';
  editedEvent: MbscCalendarEvent | null = null;
  addEditPopupAnchor: HTMLElement | undefined;
  colorPickerAnchor: HTMLElement | undefined;
  isEdit = false;
  isSuccess = false;

  myView: MbscEventcalendarView = { calendar: { labels: true } };

  colors: string[] = ['#ffeb3c', '#ff9900', '#f44437', '#ea1e63', '#9c26b0', '#3f51b5', '', '#009788', '#4baf4f', '#7e5d4e'];

  myEvents: MbscCalendarEvent[] = [
    {
      id: 1,
      start: '2026-10-08T13:00',
      end: '2026-10-08T13:45',
      title: "Lunch @ Butcher's",
      description: '',
      allDay: false,
      bufferBefore: 15,
      free: true,
      color: '#009788',
    },
    {
      id: 2,
      start: '2026-10-20T15:00',
      end: '2026-10-20T16:00',
      title: 'Conference',
      description: '',
      allDay: false,
      bufferBefore: 30,
      free: false,
      color: '#ff9900',
    },
    {
      id: 3,
      start: '2026-10-19T18:00',
      end: '2026-10-19T22:00',
      title: 'Site Visit',
      description: '',
      allDay: false,
      bufferBefore: 60,
      free: true,
      color: '#3f51b5',
    },
    {
      id: 4,
      start: '2026-10-21T10:30',
      end: '2026-10-21T11:30',
      title: 'Stakeholder mtg.',
      description: '',
      allDay: false,
      free: false,
      color: '#f44437',
    },
  ];

  editButtons: (MbscPopupButton | 'ok' | 'close' | 'set' | 'cancel')[] = [
    'cancel',
    {
      text: 'Save',
      keyCode: 'enter',
      cssClass: 'mbsc-popup-button-primary',
      handler: () => {
        const updatedEvent: MbscCalendarEvent = this.getEventData();
        const index = this.myEvents.findIndex((x) => x.id === updatedEvent.id);
        const newEventList = [...this.myEvents];

        // Update event in the list
        newEventList.splice(index, 1, updatedEvent);
        this.myEvents = newEventList;

        this.calendar.navigateToEvent(updatedEvent);
        this.addEditPopup.close();
      },
    },
  ];

  addButtons: (MbscPopupButton | 'ok' | 'close' | 'set' | 'cancel')[] = [
    'cancel',
    {
      text: 'Add',
      keyCode: 'enter',
      cssClass: 'mbsc-popup-button-primary',
      handler: () => {
        const newEvent: MbscCalendarEvent = this.getEventData();

        // Add new event to the list
        this.myEvents = [...this.myEvents, newEvent];

        this.isSuccess = true;
        this.calendar.navigateToEvent(newEvent);
        this.addEditPopup.close();
      },
    },
  ];

  addEditPopupResponsive: MbscResponsiveOptions<MbscPopupOptions> = {
    medium: {
      display: 'anchored',
      width: 400,
      fullScreen: false,
      touchUi: false,
    },
  };

  colorPickerButtons: (MbscPopupButton | 'ok' | 'close' | 'set' | 'cancel')[] = [
    'cancel',
    {
      text: 'Set',
      keyCode: 'enter',
      handler: () => this.applySelectedColor(this.selectedColor),
      cssClass: 'mbsc-popup-button-primary',
    },
  ];

  colorPickerResponsive: MbscResponsiveOptions<MbscPopupOptions> = {
    medium: {
      display: 'anchored',
      buttons: [],
      touchUi: false,
    },
  };

  fillPopup(event: MbscCalendarEvent): void {
    this.eventId = event.id;
    this.eventTitle = event.title || '';
    this.eventDescription = event['description'] || '';
    this.eventAllDay = event.allDay!;
    this.eventDates = [event.start!, event.end!];
    this.eventBuffer = event.bufferBefore || 0;
    this.eventColor = event.color || '';
    this.eventStatus = event['free'] || false;
    this.statusValue = event['free'] ? 'free' : 'busy';
  }

  createEditPopup(event: MbscCalendarEvent, target: HTMLElement): void {
    this.isEdit = true;
    this.editedEvent = event;
    this.addEditPopupAnchor = target;
    this.fillPopup(event);
    this.addEditPopup.open();
  }

  createAddPopup(event: MbscCalendarEvent, target: HTMLElement): void {
    this.isSuccess = false;
    this.isEdit = false;
    this.editedEvent = event;
    this.addEditPopupAnchor = target;
    this.fillPopup({ ...event, allDay: false });
    this.addEditPopup.open();
  }

  getEventData(): MbscCalendarEvent {
    return {
      id: this.eventId,
      title: this.eventTitle,
      description: this.eventDescription,
      allDay: this.eventAllDay,
      start: this.eventDates[0],
      end: this.eventDates[1],
      bufferBefore: this.eventBuffer,
      color: this.eventColor,
      free: this.statusValue === 'free',
    };
  }

  handleAddEditPopupClose(): void {
    if (!this.isEdit && !this.isSuccess) {
      // Refresh the list, if add popup was canceled, to remove the temporary event
      this.myEvents = [...this.myEvents];
    }
  }

  handleEventClick(args: MbscEventClickEvent): void {
    this.createEditPopup(args.event, args.domEvent.currentTarget);
  }

  handleEventCreated(args: MbscEventCreatedEvent): void {
    setTimeout(() => {
      this.createAddPopup(args.event, args.target!);
    });
  }

  handleEventDeleted() {
    this.notify.snackbar({
      button: {
        action: () => {
          this.myEvents = [...this.myEvents, this.editedEvent!];
        },
        text: 'Undo',
      },
      message: 'Event deleted',
    });
  }

  handleDeleteButtonClick() {
    const filteredEvents = this.myEvents.filter((e) => e.id !== this.editedEvent!.id);
    this.myEvents = filteredEvents;
    this.addEditPopup.close();
    this.notify.snackbar({
      button: {
        action: () => {
          this.myEvents = [...this.myEvents, this.editedEvent!];
        },
        text: 'Undo',
      },
      message: 'Event deleted',
    });
  }

  handleEventColorClick(ev: MouseEvent): void {
    this.colorPickerAnchor = ev.currentTarget as HTMLElement;
    this.colorPicker.open();
  }

  handleColorChange(ev: MouseEvent): void {
    const color = (ev.currentTarget as HTMLDivElement).getAttribute('data-value') || '';
    this.eventColor = color;
    this.selectedColor = color;
    if (!this.colorPicker.s.buttons!.length) {
      this.applySelectedColor(color);
    }
  }

  applySelectedColor(color: string): void {
    this.eventColor = color;
    this.colorPicker.close();
  }
}
```

### Template

```html
<mbsc-eventcalendar
  #calendar
  [data]="myEvents"
  [clickToCreate]="true"
  [dragToCreate]="true"
  [dragToMove]="true"
  [dragToResize]="true"
  [view]="myView"
  (onEventClick)="handleEventClick($event)"
  (onEventCreated)="handleEventCreated($event)"
  (onEventDeleted)="handleEventDeleted()"
></mbsc-eventcalendar>

<mbsc-popup
  #addEditPopup
  display="bottom"
  [contentPadding]="false"
  [fullScreen]="true"
  [scrollLock]="false"
  [headerText]="isEdit ? 'Edit event' : 'New Event'"
  [anchor]="addEditPopupAnchor"
  [buttons]="isEdit ? editButtons: addButtons"
  [responsive]="addEditPopupResponsive"
  (onClose)="handleAddEditPopupClose()"
>
  <div class="mbsc-form-group">
    <mbsc-input label="Title" [(ngModel)]="eventTitle"></mbsc-input>
    <mbsc-textarea label="Description" [(ngModel)]="eventDescription"></mbsc-textarea>
  </div>

  <div class="mbsc-form-group">
    <div>
      <mbsc-switch label="All-day" [(ngModel)]="eventAllDay"></mbsc-switch>

      <mbsc-datepicker
        [(ngModel)]="eventDates"
        select="range"
        display="anchored"
        [touchUi]="true"
        [showRangeLabels]="false"
        [controls]="eventAllDay ? ['date'] : ['datetime']"
        [responsive]="eventAllDay ? { medium: { controls: ['calendar'], touchUi: false } } : { medium: { controls: ['calendar', 'time'], touchUi: false } }"
        [startInput]="startInput"
        [endInput]="endInput"
      ></mbsc-datepicker>

      <mbsc-input #startInput label="Starts"></mbsc-input>
      <mbsc-input #endInput label="Ends"></mbsc-input>

      <mbsc-dropdown [(ngModel)]="eventBuffer" label="Travel time" *ngIf="!eventAllDay">
        <option value="0">None</option>
        <option value="5">5 minutes</option>
        <option value="15">15 minutes</option>
        <option value="30">30 minutes</option>
        <option value="60">1 hour</option>
        <option value="90">1.5 hours</option>
        <option value="120">2 hours</option>
      </mbsc-dropdown>
    </div>

    <div class="mbsc-flex mds-crud-event-color-cont" (click)="handleEventColorClick($event)">
      <div class="mbsc-flex-1-0">Color</div>
      <div class="mds-crud-selected-event-color" [ngStyle]="{background: eventColor}"></div>
    </div>

    <mbsc-segmented-group [(ngModel)]="statusValue">
      <mbsc-segmented value="busy">Show as busy</mbsc-segmented>
      <mbsc-segmented value="free">Show as free</mbsc-segmented>
    </mbsc-segmented-group>

    <div *ngIf="isEdit" class="mbsc-button-group">
      <mbsc-button class="mbsc-button-block" color="danger" variant="outline" (click)="handleDeleteButtonClick()"
        >Delete event
      </mbsc-button>
    </div>
  </div>
</mbsc-popup>

<mbsc-popup
  #colorPicker
  display="bottom"
  [contentPadding]="false"
  [showArrow]="false"
  [showOverlay]="false"
  [anchor]="colorPickerAnchor"
  [buttons]="colorPickerButtons"
  [responsive]="colorPickerResponsive"
>
  <div class="mbsc-flex mds-crud-color-row">
    <div *ngFor="let color of colors;let i=index">
      <div
        *ngIf="i<5"
        [class]="'mds-crud-color-value' + (eventColor === color ? ' mds-crud-color-value-selected' : '')"
        [attr.data-value]="color"
        (click)="handleColorChange($event)"
      >
        <div class="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" [ngStyle]="{background: color}"></div>
      </div>
    </div>
  </div>
  <div class="mbsc-flex mds-crud-color-row">
    <div *ngFor="let color of colors;let i=index">
      <div
        *ngIf="i>=5"
        [class]="'mds-crud-color-value' + (eventColor === color ? ' mds-crud-color-value-selected' : '')"
        [attr.data-value]="color"
        (click)="handleColorChange($event)"
      >
        <div class="mds-crud-color mbsc-icon mbsc-font-icon mbsc-icon-material-check" [ngStyle]="{background: color}"></div>
      </div>
    </div>
  </div>
</mbsc-popup>
```

### CSS

```css
.mds-crud-event-color-cont {
  margin: 16px;
  align-items: center;
  cursor: pointer;
}

.mds-crud-selected-event-color {
  width: 30px;
  height: 30px;
  border-radius: 15px;
  margin-right: 10px;
  margin-left: 240px;
  background: #5ac8fa;
}

.mds-crud-color-row {
  justify-content: center;
  margin: 5px;
}

.mds-crud-color {
  position: relative;
  min-width: 46px;
  min-height: 46px;
  margin: 2px;
  cursor: pointer;
  border-radius: 23px;
  background: #5ac8fa;
}

.mds-crud-color:before {
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -10px;
  margin-left: -10px;
  color: #f7f7f7;
  font-size: 20px;
  text-shadow: 0 0 3px #000;
  display: none;
}

.mds-crud-color-value {
  padding: 3px;
  margin: 2px;
}

.mds-crud-color-value.mds-crud-color-value-selected,
.mds-crud-color-value:hover {
  box-shadow: inset 0 0 0 3px #007bff;
  border-radius: 48px;
}

.mds-crud-color-value.mds-crud-color-value-selected .mds-crud-color:before {
  display: block;
}
```

## Implementation notes

- Use `view: { calendar: { labels: true } }`. Enable all four interaction modes: `clickToCreate` (React/Vue/Angular: `true`; JS/jQuery: `'double'`), `dragToCreate: true`, `dragToMove: true`, `dragToResize: true`.
- Start with 4 sample events using `dyndatetime` offsets. Each event carries: `id`, `title`, `description`, `allDay`, `start`, `end`, `bufferBefore`, `free`, and `color`.
- Handle three calendar events: `onEventClick` → opens the edit popup; `onEventCreated` → opens the add popup (the event is already temporarily placed on the calendar); `onEventDeleted` → shows a snackbar ("Event deleted") with an "Undo" button that adds the event back. Angular: use the `Notifications` service for the snackbar.
- Maintain an `isEdit` flag and an `editedEvent` reference. A `fillPopup(event)` helper populates all form fields from the event object.
- **Edit mode**: set `isEdit = true`, store `editedEvent`, set popup anchor to the clicked element, call `fillPopup`, open with "Edit event" header and a "Save" button.
- **Add mode**: set `isEdit = false`, set `isSuccess = false`, call `fillPopup`, open with "New Event" header and an "Add" button.
- **On popup close (add mode, not confirmed)**: if `isEdit` is false and `isSuccess` is false, refresh the events array (spread trick) to strip the temporary event; for the imperative API, call `calendar.removeEvent(event)` in the popup's `onClose` handler.
- **"Add" button**: build the event from form state, append to the events array, set `isSuccess = true`, call `inst.navigateToEvent(newEvent)`, close popup.
- **"Save" button**: build updated event from form state, find by `id` and splice in place, call `inst.navigateToEvent(updatedEvent)`, close popup.
- **Popup form fields**: **Title** (`Input`), **Description** (`Textarea`), **All-day** (`Switch` - when on, switches the date picker to date-only and removes the Travel time row), **Start/End** (a `Datepicker` in range mode with `startInput`/`endInput`; controls are `['datetime']` normally, `['date']` for all-day), **Travel time** (`Dropdown` with None/5/15/30 min/1 h/1.5 h/2 h options mapped to `bufferBefore` in minutes - hidden when all-day), **Color** (a clickable row opening a color picker popup with 10 swatches), **Show as busy / free** (`SegmentedGroup` mapping to the `free` boolean), and a **Delete event** button (visible in edit mode only).
- The color picker is a separate `Popup` with 10 color swatches in 2 rows of 5: `#ffeb3c`, `#ff9900`, `#f44437`, `#ea1e63`, `#9c26b0` / `#3f51b5`, empty string (no color), `#009788`, `#4baf4f`, `#7e5d4e`. On desktop (`display: 'anchored'`, no buttons): clicking applies and closes immediately. On mobile: bottom-sheet with "Set"/"Cancel". The active swatch is highlighted.
- **Delete from popup**: filter the event from the array by `id`, close the popup, show a snackbar with "Undo".

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-angular.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config angular
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   ng serve
   ```

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/angular/getting-started/installation

## Related demos

- Next: [Recurring Events Editor](https://demo.mobiscroll.com/angular/eventcalendar/recurring-event-add-edit-dialog.md) - Add recurring event management to a Angular event calendar — use preset daily, weekly, monthly, and yearly rules or a custom recurrence editor. For Angular 22 and Ionic 8.
