このガイドで作るのは、会議室を予約するためのカレンダーです。会議室ごとに1行ずつ並ぶタイムライン、ドラッグで配置できる予約、同じ会議室が二重に予約されないようにするルール、そして予約をどのカレンダーにも追加できるファイルを備えています。利用するのは @needless-ui/angular のスケジューラーです。Needless UI 全体と同じく MIT ライセンスなので、リソースタイムラインをどんなアプリでも無料で使えます。
会議室と予約
会議室はリソースです。id と title を持ち、そのほかの情報は data に入れます。予約は予定で、2026-10-23T09:00 のようなシンプルな ISO 形式の時刻と、resource に入れた会議室の id を持ちます。1件の予約に、複数の会議室や人をリストで指定することもできます。
会議室ごとに1行
両方を、view="timeline" を指定した nui-scheduler に渡します。dayStart と dayEnd で1日の表示を会議室の利用時間に絞り、snapMinutes で予約を15分単位で動かし、businessHours で会議室が閉まっている時間を網掛けにします。各行のヘッダーは nuiSchedulerResource で描画します。
<nui-scheduler
[events]="bookings()"
[resources]="rooms"
view="timeline"
dayStart="8"
dayEnd="19"
snapMinutes="15"
[businessHours]="hours"
editable
selectable
[allow]="free"
(slotSelect)="book($event)"
(eventChange)="move($event)"
(rangeChange)="range.set($event)"
>
<ng-template nuiSchedulerResource let-room>{{ room.title }}</ng-template>
</nui-scheduler>
<button nuiButton type="button" (click)="download()">Add to calendar</button>予約と移動
スケジューラーがデータを自ら変更することはなく、ユーザーの操作を通知するだけです。会議室の行に沿ってドラッグすると時間を選べ、slotSelect がその時間を会議室と一緒に通知します。予約やその終端をドラッグすると、eventChange が新しい時刻を通知し、別の会議室に移した場合は新しい会議室も通知します。シグナルを更新すれば、タイムラインもそれに従います。
import { Component, signal } from '@angular/core';
import { NuiButton } from '@needless-ui/angular/button';
import {
NuiScheduler,
type NuiSchedulerChange,
type NuiSchedulerEvent,
NuiSchedulerResourceTemplate,
type NuiSchedulerSlot,
} from '@needless-ui/angular/scheduler';
import { downloadIcs } from './booking-ics';
@Component({
selector: 'app-bookings',
imports: [NuiButton, NuiScheduler, NuiSchedulerResourceTemplate],
templateUrl: './booking.html',
})
export class Bookings {
/** Rooms are resources; bookings are events, with their room in `resource`. */
protected readonly rooms = [
{ id: 'atlas', title: 'Atlas' },
{ id: 'boreal', title: 'Boreal' },
];
protected readonly bookings = signal<NuiSchedulerEvent[]>([]);
/** Open on weekdays from 8:00 to 19:00: other time is shaded. */
protected readonly hours = [{ days: [1, 2, 3, 4, 5], start: '08:00', end: '19:00' }];
/** The days on screen: load their bookings when it changes. */
protected readonly range = signal<{ start: string; end: string } | null>(null);
protected book({ start, end, resource }: NuiSchedulerSlot): void {
const booking = { id: crypto.randomUUID(), title: 'New booking', start, end, resource };
this.bookings.update((all) => [...all, booking]);
}
protected move({ event, start, end, resource }: NuiSchedulerChange): void {
this.bookings.update((all) =>
all.map((b) =>
b.id === event.id ? { ...b, start, end, resource: resource ?? b.resource } : b,
),
);
}
/** A room holds one booking at a time. */
protected readonly free = (time: NuiSchedulerChange | NuiSchedulerSlot): boolean => {
const id = 'event' in time ? time.event.id : null;
const room = 'event' in time ? (time.resource ?? time.event.resource) : time.resource;
return !this.bookings().some(
(b) => b.id !== id && b.resource === room && b.start < time.end && b.end > time.start,
);
};
protected download(): void {
downloadIcs(this.bookings(), 'bookings');
}
}二重予約を防ぐ
予約を作成または移動する前に、スケジューラーは allow に可否を確認します。false が返ると、ドラッグ中の表示は赤くなり、ドロップしても何も起きません。キーボードで操作している場合は、その時間は利用できないとスケジューラーが読み上げます。この例では同じ会議室のほかの予約を確認していますが、最終的に判断するのはサーバーなので、サーバーでも改めて確認してください。
表示中の日を読み込む
rangeChange は、表示される最初と最後の日を、開始時に1回、その後はユーザーがページを移るたびに通知します。そのため、その期間の予約だけを、httpResource やお好みのリクエストで取得できます。
カレンダーに追加
nuiSchedulerToIcs() は、予約を iCalendar ファイルとして書き出します。Google Calendar、Outlook、Apple Calendar でインポートできるファイルです。nuiSchedulerFromIcs() は逆に iCalendar ファイルを読み込み、ほかのタイムゾーンの時刻をユーザーのタイムゾーンに合わせます。
import { type NuiSchedulerEvent, nuiSchedulerToIcs } from '@needless-ui/angular/scheduler';
/** Saves events as an .ics file, which Google Calendar, Outlook and Apple Calendar import. */
export function downloadIcs(events: readonly NuiSchedulerEvent[], name: string): void {
const file = new Blob([nuiSchedulerToIcs(events, { name })], { type: 'text/calendar' });
const link = document.createElement('a');
link.href = URL.createObjectURL(file);
link.download = `${name}.ics`;
link.click();
URL.revokeObjectURL(link.href);
}キーボードとスクリーンリーダー
どの操作もマウスなしで行えます。タイムラインのタブストップは1つだけで、矢印キーで時間と会議室の間を移動し、Shift キーと矢印キーで時間を選び、Enter キーで予約します。予約の上で Alt キーと矢印キーを押すと、別の時間や会議室に移動します。各予約は、タイトル、会議室、日付、時刻で名前が付いたボタンです。
会議室を横に並べた日表示、繰り返しの予約、そのほかのすべてのオプションについては、スケジューラーのページとその API をご覧ください。