【Angular】Angular-calendarでスケジューラーを実装する

作業環境

  • 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">&times;</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 をインストールしてマテリアルデザインを実装する

オススメ参考書

ブログ運営者

・関連記事







プログラミングの独学はとても難しい


プログラミングは小学校の義務教育にも導入され始めており、これから社会人として生きていく上でプログラミングはもはや出来て当たり前、出来なれば論外というエクセルレベルの必須スキルになりつつあります。そしてそういう話を聞いて参考書なりを購入して独学でプログラミング勉強しようと思っている人も少なくないでしょう。しかしプログラミングを独学で勉強し始めようと思うものの



・「分からない箇所で詰まって挫折してしまった」

・「勉強する時間が足りない」

・「ネットの記事だと情報が断片的でよくわからない」

・「コードのエラーの原因が分からない」



という壁にぶち当たって、プログラミングの勉強を止めてしまう方が少なくありません。独学でプログラミングを勉強してる時間のほとんどはつまづいている時間です。実際僕も最初のころ独学でプログラミングを勉強していた頃はエラーの原因が分からず丸1日を不意にしてしまった・・・そんな苦い経験がありました。それで僕は一度はプログラミングの学習を諦めてしまいましたが、就活で現実を知る中で



「プログラミングを勉強して、いずれフリーランスとして自由な生き方がしたい」

「エンジニアとして若いうちから高収入を得たい」



という強い気持ちから一念発起して「侍エンジニアのwebサービスコース」に申し込み、プロのエンジニアの方に対面でマンツーマンでPythonによるWebサービス作り方とWeb技術の基本を教えてもらい、ようやくプログラミングが理解でき、今ではエンジニアとしてそこそこの暮らしができるようになりました。





侍エンジニアでは、とりあえずプログラミングやインターネットの基本を知っておきたい人から、HTML・cssなどでWebサイトやWebアプリを作ってみたい人やPythonを勉強してデータサイエンティストやAIエンジニアになりたい人まで幅広いニーズに応えた様々なコースが用意されています。



IT業界と言ってもエンジニアの仕事はプログラミング言語次第でサーバーから機械学習・ディープラーニングまで多種多様ですし、侍エンジニアの無料レッスン(カウンセリング)を受けてみて、自分のやりたいITの仕事は何なのか?を見つけるのがエンジニアへの第一歩になります。ちなみに今侍エンジニアの無料レッスンを受けると1000円分のAmazonギフト券がもらえるので、試しに受けてみるだけもお得です。


自分は半端に独学やオンラインスクールで勉強して金と時間を無駄にするくらいなら、リアルのプログラミングスクールに通ってしっかりプログラミングを勉強した方がいいと思います。ちなみに今、侍エンジニアに申し込むと、25歳以下の学生の方であれば、受講料が20%OFFになるので超お得です。


そして、プログラミングは大勢で授業を受けたり漫然とオンライン学習をするよりも自分が分からない箇所をピンポイントでプロの講師に直接質問して、ちゃんと納得するというスタイルの方がお金は確かに少し掛かりますが、独学で学ぶよりも絶対にモノになります。


シェアする

  • このエントリーをはてなブックマークに追加

フォローする