作業環境
- Windows10
- Angular8.23
ライブラリのインストール
# 必要なライブラリのインストール $ ng add angular-calendar $ npm install --save flatpickr angularx-flatpickr $ npm install --save @ng-bootstrap/ng-bootstrap@5.0.0 $ npm install --save @angular/flex-layout@8.0.0-beta.27 # コンポーネントを追加 $ ng generate component ngcalendar-sample
ng-bootstrapとflex-layoutはangularのバージョン違いでエラーよく吐くので、最新版以外を使用する場合は@でバージョン指定する必要があります。
<app-routing.module.ts>
import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; ~~~~ ~~~~ import { NgcalendarSampleComponent } from "./ngcalendar-sample/ngcalendar-sample.component"; //これを追加! const routes: Routes = [ ~~~~ ~~~~ { path: "angular-calendar-sample", component: NgcalendarSampleComponent },//これを追加! ];
<app.module.ts>
import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; ~~~~ ~~~~ import { NgcalendarSampleComponent } from './ngcalendar-sample/ngcalendar-sample.component'; //これを追加! import { CalendarModule, DateAdapter } from 'angular-calendar'; //これを追加! import { adapterFactory } from 'angular-calendar/date-adapters/date-fns'; //これを追加! import { NgbModalModule } from "@ng-bootstrap/ng-bootstrap";//これを追加! import { FlatpickrModule } from "angularx-flatpickr";//これを追加! import { FormsModule } from "@angular/forms";//これを追加! import { CommonModule } from "@angular/common";//これを追加! import { MatButtonToggleModule } from "@angular/material/button-toggle";//これを追加! import { FlexLayoutModule } from "@angular/flex-layout";//これを追加! @NgModule({ declarations: [ AppComponent, ~~~~ ~~~~ NgcalendarSampleComponent, ], imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, ~~~~ ~~~~ CalendarModule.forRoot({ provide: DateAdapter, useFactory: adapterFactory }), //これを追加! CommonModule,//これを追加! FormsModule,//これを追加! FlatpickrModule.forRoot(),//これを追加! NgbModalModule,//これを追加! MatButtonToggleModule,//これを追加! FlexLayoutModule//これを追加! ], ~~~~ ~~~~ providers: [], bootstrap: [AppComponent], exports: [NgcalendarSampleComponent],//これを追加! }) export class AppModule {}
<styles.scss>
~~~~ ~~~~ @import 'flatpickr/dist/flatpickr.css'
スケジューラー画面の実装
<ngcalendar-sample.component.ts>
import { Component, ChangeDetectionStrategy, ViewChild, TemplateRef, OnInit, } from "@angular/core"; import { startOfDay, endOfDay, subDays, addDays, endOfMonth, isSameDay, isSameMonth, addHours, } from "date-fns"; import { Subject } from "rxjs"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { CalendarEvent, CalendarEventAction, CalendarEventTimesChangedEvent, CalendarView, } from "angular-calendar"; const colors: any = { red: { primary: "#ad2121", secondary: "#FAE3E3", }, blue: { primary: "#1e90ff", secondary: "#D1E8FF", }, yellow: { primary: "#e3bc08", secondary: "#FDF1BA", }, }; @Component({ selector: "app-ngcalendar-sample", templateUrl: "./ngcalendar-sample.component.html", styleUrls: ["./ngcalendar-sample.component.css"], changeDetection: ChangeDetectionStrategy.OnPush, }) export class NgcalendarSampleComponent implements OnInit { @ViewChild("modalContent", { static: true }) modalContent: TemplateRef; view: CalendarView = CalendarView.Month; CalendarView = CalendarView; viewDate: Date = new Date(); modalData: { action: string; event: CalendarEvent; }; actions: CalendarEventAction[] = [ { label: '', a11yLabel: "Edit", onClick: ({ event }: { event: CalendarEvent }): void => { this.handleEvent("Edited", event); }, }, { label: '', a11yLabel: "Delete", onClick: ({ event }: { event: CalendarEvent }): void => { this.events = this.events.filter((iEvent) => iEvent !== event); this.handleEvent("Deleted", event); }, }, ]; refresh: Subject = new Subject(); events: CalendarEvent[] = [ { start: subDays(startOfDay(new Date()), 1), end: addDays(new Date(), 1), title: "A 3 day event", color: colors.red, actions: this.actions, allDay: true, resizable: { beforeStart: true, afterEnd: true, }, draggable: true, }, { start: startOfDay(new Date()), title: "An event with no end date", color: colors.yellow, actions: this.actions, }, { start: subDays(endOfMonth(new Date()), 3), end: addDays(endOfMonth(new Date()), 3), title: "A long event that spans 2 months", color: colors.blue, allDay: true, }, { start: addHours(startOfDay(new Date()), 2), end: addHours(new Date(), 2), title: "A draggable and resizable event", color: colors.yellow, allDay: true, // actions: this.actions, // resizable: { // beforeStart: true, // afterEnd: true, // }, // draggable: true, }, ]; activeDayIsOpen: boolean = true; constructor(private modal: NgbModal) {} dayClicked({ date, events }: { date: Date; events: CalendarEvent[] }): void { if (isSameMonth(date, this.viewDate)) { if ( (isSameDay(this.viewDate, date) && this.activeDayIsOpen === true) || events.length === 0 ) { this.activeDayIsOpen = false; } else { this.activeDayIsOpen = true; } this.viewDate = date; } } eventTimesChanged({ event, newStart, newEnd, }: CalendarEventTimesChangedEvent): void { this.events = this.events.map((iEvent) => { if (iEvent === event) { return { ...event, start: newStart, end: newEnd, }; } return iEvent; }); this.handleEvent("Dropped or resized", event); } handleEvent(action: string, event: CalendarEvent): void { this.modalData = { event, action }; this.modal.open(this.modalContent, { size: "lg" }); } addEvent(): void { this.events = [ ...this.events, { title: "New event", start: startOfDay(new Date()), end: endOfDay(new Date()), color: colors.red, draggable: true, resizable: { beforeStart: true, afterEnd: true, }, }, ]; } deleteEvent(eventToDelete: CalendarEvent) { this.events = this.events.filter((event) => event !== eventToDelete); } setView(view: CalendarView) { this.view = view; } closeOpenMonthViewDay() { this.activeDayIsOpen = false; } ngOnInit() {} }
<ngcalendar-sample.component.html>
<div class="container" fxLayout="row"> <!-- <div class="col-md-4"> <div class="btn-group"> <div class="btn btn-primary" mwlCalendarPreviousView [view]="view" [(viewDate)]="viewDate" (viewDateChange)="closeOpenMonthViewDay()" > Previous </div> <div class="btn btn-outline-secondary" mwlCalendarToday [(viewDate)]="viewDate" > Today </div> <div class="btn btn-primary" mwlCalendarNextView [view]="view" [(viewDate)]="viewDate" (viewDateChange)="closeOpenMonthViewDay()" > Next </div> </div> </div> --> <div class="item item1" fxFlex="40"> <mat-button-toggle-group #group="matButtonToggleGroup"> <mat-button-toggle value="left" aria-label="Text align left"> <div class="btn btn-primary" mwlCalendarPreviousView [view]="view" [(viewDate)]="viewDate" (viewDateChange)="closeOpenMonthViewDay()"> Previous </div> </mat-button-toggle> <mat-button-toggle value="center" aria-label="Text align center"> <div class="btn btn-outline-secondary" mwlCalendarToday [(viewDate)]="viewDate"> Today </div> </mat-button-toggle> <mat-button-toggle value="right" aria-label="Text align right"> <div class="btn btn-primary" mwlCalendarNextView [view]="view" [(viewDate)]="viewDate" (viewDateChange)="closeOpenMonthViewDay()"> Next </div> </mat-button-toggle> </mat-button-toggle-group> </div> <div class="item item2" fxFlex="20"> <strong> {{ viewDate | calendarDate: view + "ViewTitle":"en" }} </strong> </div> <div class="item item3" fxFlex="40"> <mat-button-toggle-group #group="matButtonToggleGroup"> <mat-button-toggle value="left" aria-label="Text align left"> <div class="btn btn-primary" (click)="setView(CalendarView.Month)" [class.active]="view === CalendarView.Month"> Month </div> </mat-button-toggle> <mat-button-toggle value="center" aria-label="Text align center"> <div class="btn btn-primary" (click)="setView(CalendarView.Week)" [class.active]="view === CalendarView.Week"> Week </div> </mat-button-toggle> <mat-button-toggle value="right" aria-label="Text align right"> <div class="btn btn-primary" (click)="setView(CalendarView.Day)" [class.active]="view === CalendarView.Day"> Day </div> </mat-button-toggle> </mat-button-toggle-group> <!-- <div class="btn-group"> <div class="btn btn-primary" (click)="setView(CalendarView.Month)" [class.active]="view === CalendarView.Month" > Month </div> <div class="btn btn-primary" (click)="setView(CalendarView.Week)" [class.active]="view === CalendarView.Week" > Week </div> <div class="btn btn-primary" (click)="setView(CalendarView.Day)" [class.active]="view === CalendarView.Day" > Day </div> </div> --> </div> </div> <br /> <div [ngSwitch]="view"> <mwl-calendar-month-view *ngSwitchCase="CalendarView.Month" [viewDate]="viewDate" [events]="events" [refresh]="refresh" [activeDayIsOpen]="activeDayIsOpen" (dayClicked)="dayClicked($event.day)" (eventClicked)="handleEvent('Clicked', $event.event)" (eventTimesChanged)="eventTimesChanged($event)"> </mwl-calendar-month-view> <mwl-calendar-week-view *ngSwitchCase="CalendarView.Week" [viewDate]="viewDate" [events]="events" [refresh]="refresh" (eventClicked)="handleEvent('Clicked', $event.event)" (eventTimesChanged)="eventTimesChanged($event)"> </mwl-calendar-week-view> <mwl-calendar-day-view *ngSwitchCase="CalendarView.Day" [viewDate]="viewDate" [events]="events" [refresh]="refresh" (eventClicked)="handleEvent('Clicked', $event.event)" (eventTimesChanged)="eventTimesChanged($event)"> </mwl-calendar-day-view> </div> <!-- Everything you see below is just for the demo, you don't need to include it in your app --> <br /><br /><br /> <h3> Edit events <button class="btn btn-primary pull-right" (click)="addEvent()"> Add new </button> <div class="clearfix"></div> </h3> <div class="table-responsive"> <table class="table table-bordered"> <thead> <tr> <th>Title</th> <th>Primary color</th> <th>Secondary color</th> <th>Starts at</th> <th>Ends at</th> <th>Remove</th> </tr> </thead> <tbody> <tr *ngFor="let event of events"> <td> <input type="text" class="form-control" [(ngModel)]="event.title" (keyup)="refresh.next()" /> </td> <td> <input type="color" [(ngModel)]="event.color.primary" (change)="refresh.next()" /> </td> <td> <input type="color" [(ngModel)]="event.color.secondary" (change)="refresh.next()" /> </td> <td> <input class="form-control" type="text" mwlFlatpickr [(ngModel)]="event.start" (ngModelChange)="refresh.next()" [altInput]="true" [convertModelValue]="true" [enableTime]="true" dateFormat="Y-m-dTH:i" altFormat="F j, Y H:i" placeholder="Not set" /> </td> <td> <input class="form-control" type="text" mwlFlatpickr [(ngModel)]="event.end" (ngModelChange)="refresh.next()" [altInput]="true" [convertModelValue]="true" [enableTime]="true" dateFormat="Y-m-dTH:i" altFormat="F j, Y H:i" placeholder="Not set" /> </td> <td> <button class="btn btn-danger" (click)="deleteEvent(event)"> Delete </button> </td> </tr> </tbody> </table> </div> <ng-template #modalContent let-close="close"> <div class="modal-header"> <h5 class="modal-title">Event action occurred</h5> <button type="button" class="close" (click)="close()"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div> Action: <pre>{{ modalData?.action }}</pre> </div> <div> Event: <pre>{{ modalData?.event | json }}</pre> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-outline-secondary" (click)="close()"> OK </button> </div> </ng-template>
http://localhost:4200/angular-calendar-sampleにアクセスすると以下のような画面が表示されます。
weekタブをクリックすると以下のように週単位のスケジューラーが表示されます。
dayタブをクリックすると以下のように日にち単位のスケジューラーが表示されます
fullcalendarは週/日のスケジューラーが有料なので、それを無料で提供しているのはangular-calendarの強みですね。日本語設定とかが分かりにくいですが
関連記事
・【Angular】fullcalendarでGoogleカレンダーライクな画面を実装する
・AngularによるWebアプリ開発②~Angular Material をインストールしてマテリアルデザインを実装する
オススメ参考書
コメント