Installation
import { WrEventCalendar } from 'ngwr/event-calendar';
@Component({ imports: [WrEventCalendar] })
export class MyComponent {}Register a date adapter
Every date calculation and every label goes through WrDateAdapter, the same one the calendar and the date pickers use. Register one at bootstrap or the calendar has no way to name a month.
import { provideWrDateAdapter } from 'ngwr/date-adapter';
import { provideWrDateFnsAdapter } from 'ngwr/date-adapter-fns';
bootstrapApplication(App, {
providers: [provideWrDateFnsAdapter()],
});Views
One component covers all three views. view and date are two-way bindable, so the built-in header is a convenience rather than the only way to navigate — bind them and drive the calendar from your own toolbar, or set hideHeader.
<wr-event-calendar [events]="events()" [(view)]="view" [(date)]="anchor" />Events
end is exclusive: an event running 09:00–10:00 does not overlap one that starts at 10:00, and a single all-day event ends at midnight the next day. That one rule makes adjacency and overlap the same comparison in every view. color takes any intent, and editable: false pins an individual event while the rest of the calendar stays draggable.
protected readonly events = signal<readonly WrCalendarEvent[]>([
{ id: 1, title: 'Design review', start: at(0, 10), end: at(0, 11, 30), color: 'primary' },
{ id: 5, title: 'Conference', start: at(2, 0), end: at(5, 0), allDay: true, color: 'secondary' },
{ id: 7, title: 'Frozen — no deploys', start: at(6, 0), end: at(7, 0), allDay: true, editable: false },
]);Drag to move and resize
events is an input and the calendar never writes to it. A drag emits eventChange with where the event *would* land and stops there — apply it to your own state and the new position renders on the next pass. So ignoring the output cancels the drag, an optimistic update is a single update, and a server rejection needs no rollback path inside the component. Drag a chip below, or focus one and hold Alt with the arrow keys.
<wr-event-calendar
editable
[events]="events()"
[(view)]="view"
[(date)]="anchor"
(eventChange)="apply($event)"
/>protected apply(change: WrCalendarEventChange): void {
this.events.update(events =>
events.map(event =>
event.id === change.event.id ? { ...event, start: change.start, end: change.end } : event
)
);
}The time grid
dayStartHour and dayEndHour trim the visible band — a calendar that opens on 3am dead space wastes most of its height. slotMinutes sets both the row height and the drag snap, so a 15-minute grid drags in 15-minute steps.
<wr-event-calendar
editable
view="week"
[dayStartHour]="8"
[dayEndHour]="19"
[slotMinutes]="30"
[events]="events()"
/>Creating from empty space
Clicking anywhere that is not a chip emits slotClick with the slot the user pointed at — a whole day in month view and the all-day band, the pointed-at row otherwise. From the keyboard it is Enter on an empty cell.
<wr-event-calendar [events]="events()" (slotClick)="compose($event)" />Custom chip contents
wrCalendarEvent replaces what a chip renders. Position, sizing and drag stay with the calendar, so a custom template cannot break the geometry.
<wr-event-calendar [events]="events()">
<ng-template wrCalendarEvent let-event>
<strong>{{ event.title }}</strong>
<small>{{ event.data.room }}</small>
</ng-template>
</wr-event-calendar>Keyboard
The grid is a single tab stop with a roving cursor, so tabbing through a month does not mean tabbing through 42 cells.
- Arrow keys — move by a day left and right, by a week or a slot up and down.
- Home / End — first and last day of the row.
- Enter — on an empty cell emits `slotClick`; on a cell holding events focuses the first chip.
- Escape — from a chip, back to its cell.
- Alt + arrows — on a focused chip, move the event. The keyboard equivalent of the drag, emitting the same `eventChange`.
API
| Name | Description | Type | Default |
|---|---|---|---|
events | Everything to render. Never mutated — see eventChange. | readonly WrCalendarEvent[] | [] |
view | Which span is shown. Two-way bindable. | WrCalendarView | 'month' |
date | Any date inside the shown span. Two-way bindable. | Date | new Date() |
views | Which buttons the view switcher offers. Empty hides it. | readonly WrCalendarView[] | ['month', 'week', 'day'] |
editable | Allow dragging chips to move and resize them. | boolean | false |
slotMinutes | Minutes per row in the time views — also the drag snap. | number | 30 |
dayStartHour | First hour the time views show. | number | 0 |
dayEndHour | First hour they do NOT show, exclusive. | number | 24 |
maxLanes | Lanes a month cell shows before collapsing the rest into “+N more”. | number | 3 |
hideHeader | Hide the built-in header — supply your own navigation instead. | boolean | false |
(eventClick) | A chip was activated. | WrCalendarEvent | — |
(slotClick) | Empty space was activated — the slot the user pointed at. | WrCalendarSlot | — |
(eventChange) | A drag or an Alt + arrow finished. Apply it yourself; nothing moves until you do. | WrCalendarEventChange | — |
ariaLabel | Accessible name of the grid; overridable for a page with several. | string | null | null |