@wiltech-labs/ngx-calendar
v1.0.3
Published
Shared Angular calendar — day/week/month views on FullCalendar, Material 3 styled, with a day panel and an event detail/edit side panel.
Readme
@wiltech-labs/ngx-calendar
Day, week and month calendar for Angular apps, built on FullCalendar 7 and styled from the app's Material 3 tokens (light and dark, no palette file).
- Wide screens (≥ 1200px): event chips show time and title. Clicking a day (or "+N more") opens a side panel listing every event that day; in Day view it follows the visible day.
- Narrower screens: the same day panel sits below the calendar.
- Phones: chips show only the start time (month view draws colour bars). The selected day's events (today until the user picks another) are always listed below the calendar.
- Event panel: clicking an event opens it in an
ngx-modalsside panel: date, time, zone and description. Edit swaps in anngx-formsform; Save emitseventSave. The calendar never changeseventsitself; the owner of the data applies the change. - All-day events (leave, deadlines) span their days in month view and sit in an all-day row in day and week views; the row appears only when there is one. They open read-only.
- Holidays (
holidays) shade their day (the whole column in day and week views) and carry a chip, "Public holiday · Green Monday", ahead of the day's events; the day panel lists them first. They can't be opened; clicking one selects the day.
Install
npm install @wiltech-labs/ngx-calendarAdd FullCalendar's two stylesheets to the app's angular.json build.options.styles, before the
app's own styles. The calendar maps the theme's colours to the app's --mat-sys-* tokens itself,
so no palette stylesheet is needed:
"styles": [
"@fullcalendar/angular/skeleton.css",
"@fullcalendar/angular/themes/monarch/theme.css",
"src/styles.scss"
]Add @fullcalendar/angular as a direct dependency of the app, since angular.json references it.
Usage
import { Component, signal } from '@angular/core';
import { Calendar, type CalendarEvent, type CalendarView } from '@wiltech-labs/ngx-calendar';
@Component({
selector: 'app-schedule',
imports: [Calendar],
template: `<ngx-calendar [events]="events()" [(view)]="view" (eventSave)="save($event)" />`,
})
export class ScheduleComponent {
protected readonly view = signal<CalendarView>('week');
protected readonly events = signal<CalendarEvent[]>([
{
id: '1',
title: 'Design review',
description: 'Walk through the onboarding flow.',
startDateTime: '2026-10-01T09:00Z',
endDateTime: '2026-10-01T10:30Z',
config: { color: 'tertiary' },
},
]);
protected save(event: CalendarEvent): void {
this.events.update((events) => events.map((e) => (e.id === event.id ? event : e)));
}
}Inputs and outputs
| Name | Type | Default | Notes |
| ----------- | ---------------------------------- | --------------- | ----------------------------------------------------- |
| events | CalendarEvent[] | [] | |
| holidays | CalendarHoliday[] | [] | Public holidays and closure days. Since 1.0.3. |
| view | 'day' \| 'week' \| 'month' model | 'month' | Two-way: [(view)]. |
| timeZone | IANA zone | the user's zone | Events are UTC; they're shown in this zone. |
| editable | boolean | true | Shows the panel's Edit action. |
| firstDay | 0–6 | the locale's | 0 = Sunday. |
| eventSave | output CalendarEvent | | The edited event, start/end as 'YYYY-MM-DDTHH:mmZ'. |
| daySelect | output string | | The day the user picked, 'YYYY-MM-DD'. |
| rangeChange | output CalendarRange | | { start, end } dates on screen (both included), on first render and after each move. Since 1.0.2. |
Events
interface CalendarEvent<TSource = unknown> {
id: string;
title: string;
description: string;
startDateTime: string; // UTC, 'YYYY-MM-DDTHH:mmZ'; all-day: first day, 'YYYY-MM-DD'
endDateTime: string; // UTC, 'YYYY-MM-DDTHH:mmZ'; all-day: last day (included)
allDay?: boolean; // whole days, shown on those dates whatever the timeZone (since 1.0.1)
config?: CalendarEventConfig;
source?: TSource; // the app's own object, carried through untouched
}
interface CalendarEventConfig {
color?: 'primary' | 'secondary' | 'tertiary' | 'error' | 'neutral'; // M3 container pair
backgroundColor?: string; // overrides; prefer a token, Eg 'var(--mat-sys-primary)'
textColor?: string;
className?: string;
editable?: boolean; // false hides Edit for this event
}All-day events
Set allDay: true and give dates instead of instants, both ends included:
{
id: 'leave-42',
title: 'Eleni: annual leave',
description: '',
startDateTime: '2026-11-02',
endDateTime: '2026-11-06',
allDay: true,
config: { color: 'secondary' },
}The mapper takes an allDay reader too: allDay: (leave) => true. All-day events have no time to
edit, so their panel has no Edit action; the day list and panel show "All day"
(NGX_CALENDAR_TEXT's optional allDay, which also labels the all-day row).
Holidays
Pass the items an API using wiltech-labs-holidays
sends, as they are (date, name, category; other fields are ignored):
<ngx-calendar [events]="events()" [holidays]="store.publicHolidays()" />interface CalendarHoliday {
date: string; // 'YYYY-MM-DD'
name: string;
category?: string; // 'public' (default): "Public holiday"; anything else: "Holiday"
}The labels are NGX_CALENDAR_TEXT's optional publicHoliday and holiday.
Loading events for what's on screen
Fetch by rangeChange when the events come from an API that answers a date range:
<ngx-calendar [events]="events()" (rangeChange)="store.load($event.start, $event.end)" />Mapping the app's own data
CalendarEventMapper turns any list into CalendarEvents and keeps each original on source:
private readonly mapper = inject(CalendarEventMapper);
protected readonly events = computed(() =>
this.mapper.map(this.store.bookings(), {
id: (b) => b.id,
title: (b) => b.customerName,
description: (b) => b.notes,
startDateTime: (b) => b.startsAt,
endDateTime: (b) => b.endsAt,
config: (b) => ({ color: b.confirmed ? 'primary' : 'tertiary' }),
}),
);Locale and text
NGX_CALENDAR_LOCALE (dates, weekday names, first day of the week; default: the browser's
language) and NGX_CALENDAR_TEXT (every label; default English) are resolver tokens, the same
pattern as ngx-dates' NGX_DATES_LOCALE. Wire them to ngx-translations to follow a language
switch:
{
provide: NGX_CALENDAR_LOCALE,
useFactory: () => { const t = inject(TranslationsService); return () => t.locale(); },
}Theming
The grid reads FullCalendar's monarch (Material 3) theme variables, which this package points at
--mat-sys-* roles. Optional overrides: --ngx-calendar-background (grid surface) and
--ngx-calendar-panel-background (day panel).
Status
1.0.2 (all-day events, rangeChange) is on npm; 1.0.3 (holidays) is built, not yet
published. Consumed by apps/showcase (/calendar) and Crewly's "Who's away" page.
Publishing
cd packages/calendar
npm run buildngx-forms and ngx-modals are file:../x/dist dependencies (see root CLAUDE.md's
"Inter-package deps"); build them first, or use npm run build:packages from the repo root.
npm run build's postbuild script rewrites them to real version ranges in dist/package.json
automatically. Then:
cd dist
npm publishBump version in the source package.json before building.
