- 前端
- UI组件
【免费下载链接】fullcalendar
Full-sized drag & drop event calendar in JavaScript
导读
@fullcalendar/angular是 FullCalendar 官方为 Angular 框架提供的连接器组件,它以full-calendar自定义元素的形式把功能完整的拖拽式事件日历(event calendar)封装进 Angular 应用中。本指南以 packages/angular/README.md 为核心,结合仓库内组件源码、示例应用与测试用例,系统讲解从安装、模块注册、选项配置到嵌套模板渲染与getApi()编程式控制的完整链路,读完即可在 Angular 项目中快速落地可交互的日历功能。
1. 组件概览:一个组件,承载完整日历
FullCalendar 本身是 JavaScript 生态中的全尺寸(full-sized)事件日历库,支持拖拽(drag & drop)事件、月/周/日/列表等多种视图以及丰富的时间轴能力。@fullcalendar/angular则是把这一能力桥接到 Angular 的官方连接器,其核心是一个名为full-calendar的 Angular 组件,位于 packages/angular/lib/src/full-calendar.component.ts。
从源码结构看,该组件采用"包装器"(wrapper)模式:内部直接持有fullcalendar核心包创建的Calendar实例,并将 Angular 侧的生命周期(ngAfterViewInit、ngDoCheck、ngAfterContentChecked、ngOnDestroy)与日历的创建、选项更新、渲染恢复、销毁一一对应,同时通过CustomRenderingStore把 FullCalendar 内部的渲染回调桥接回 Angular 的模板系统(详见第 5 节)。因此,在 Angular 中使用时无需手动管理 DOM 生命周期,Angular 的变更检测机制会自动驱动日历的刷新。
2. 安装:核心包、连接器与必要的依赖
根据 packages/angular/README.md 的安装说明,需要同时安装 Angular 连接器、核心包以及temporal-polyfill(核心包 7.x 使用 TC39 Temporal API,需要一个 polyfill 以兼容当前浏览器环境):
npm install @fullcalendar/angular fullcalendar temporal-polyfill这里的依赖关系也可以在 packages/angular/lib/package.json 中得到印证:
peerDependencies声明了"fullcalendar": "workspace:7.0.2"与"temporal-polyfill": "^1.0.1";- 同时要求宿主项目提供
@angular/common、@angular/core(版本范围16 - 22,见第 4 节)。
此外,FullCalendar 的视图能力由插件提供。安装后在代码中按需引入,例如月度视图(daygrid)与主题插件:
npm install @fullcalendar/angular fullcalendar temporal-polyfill说明:
daygrid、timegrid、list、multimonth、interaction、themes/*、locales/*等子路径(sub-path)导出并非手写源码,而是构建阶段由 packages/angular/scripts/postprocess-dist.ts 依据 vanilla 包(packages/vanilla/package.json的buildConfig.exports)自动生成的重导出模块(.mjs+.d.ts),统一转发到fullcalendar/<module>。这也是 README 示例中能够直接写import dayGridPlugin from '@fullcalendar/angular/daygrid'的原因。
3. 基础用法:注册模块并渲染日历
3.1 在 AppModule 中注册 FullCalendarModule
使用前需要把FullCalendarModule导入到应用模块中(README 原文示例):
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FullCalendarModule } from '@fullcalendar/angular'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FullCalendarModule // register FullCalendar with your app ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }模块的实现位于 packages/angular/lib/src/full-calendar.module.ts:它声明了FullCalendarComponent及两个内部辅助组件(OffscreenFragmentComponent、TransportContainerComponent),仅对外导出FullCalendarComponent,公共 API 面则由 packages/angular/lib/src/index.ts 统一导出(含fullcalendar/public-api的全部类型与CalendarOptions)。
3.2 使用 full-calendar 组件并传入 options
注册完成后,在组件模板中放置<full-calendar>,并把配置对象通过[options]绑定传入(README 原文示例):
import { Component } from '@angular/core'; import { CalendarOptions } from '@fullcalendar/angular'; import dayGridPlugin from '@fullcalendar/angular/daygrid'; @Component({ selector: 'app-root', templateUrl: ` <div> <h1>Demo App</h1> <full-calendar [options]="calendarOptions"></full-calendar> </div> `, styleUrls: ['./app.component.scss'] }) export class AppComponent { calendarOptions: CalendarOptions = { plugins: [dayGridPlugin], initialView: 'dayGridMonth', weekends: false, events: [ { title: 'Meeting', start: new Date() } ] }; }其中关键配置项的含义如下:
| 配置项 | 作用 |
|---|---|
plugins | 启用哪些视图/功能插件,如dayGridPlugin(月/周视图)、timeGridPlugin、listPlugin、interactionPlugin(拖拽、缩放、日期选择) |
initialView | 初始视图类型,如dayGridMonth(月度)、timeGridWeek、listWeek |
weekends | 是否显示周末列,默认为true |
events | 事件数据源,可以是数组、JSON 请求函数或事件源配置 |
组件通过@Input() options?: CalendarOptions接收整个配置对象(见 full-calendar.component.ts)。在ngAfterViewInit中,组件会把options与独立的输入属性合并,创建new Calendar(calendarEl, {...options})并调用calendar.render()。
兼容性细节:组件源码中有一段针对 Ionic 的尺寸修正逻辑(
calendarEl.closest('ion-content')检测),若日历被放置于ion-content内,会等待componentOnReady()后再以requestAnimationFrame调度渲染,以避免滚动容器未就绪导致的渲染问题(对应 GitHub issue #4976)。普通 Angular 应用则直接调用render()。
3.3 events / eventSources / resources:顶层独立输入
除整个options对象外,组件还暴露了三个高频数据的独立@Input():
@Input() events?: CalendarOption<'events'> | null | undefined; @Input() eventSources?: CalendarOption<'eventSources'> | null | undefined; @Input() resources?: CalendarOption<'resources'> | null | undefined;对应 packages/angular/lib/src/options.ts 中维护的OPTION_INPUT_NAMES列表。这意味着你可以这样把事件数组单独绑定,便于与 Angular 表单/服务联动:
<full-calendar [options]="calendarOptions" [events]="events" ></full-calendar>在ngDoCheck阶段,组件会把这类独立输入合并进新的选项快照参与 diff(见第 5.2 节),因此events数组被替换时日历会自动重渲染——这一行为在 full-calendar.component.spec.ts 的HostComponentWithEventAttr测试中得到了验证(addEventReset()后事件渲染计数从 1 变为 3)。
4. 支持的 Angular 版本
README 明确指出:@fullcalendar/angular7.x 支持 Angular 16 至 22。这一点与 packages/angular/lib/package.json 中peerDependencies的"@angular/common": "16 - 22"、"@angular/core": "16 - 22"完全一致。
从 packages/angular/CHANGELOG.md 可以回溯版本演进脉络:6.0.0-beta.3 引入ng-template自定义模板渲染、把events/eventSources/resources提升为顶层输入,并把最低 Angular 版本要求提升到 12;更早的 4.x/5.x 系列则记录了历次 Angular 10–13 的兼容支持。当前 7.x 由fullcalendar核心 7.0.2 驱动,底层已切换至 TC39 Temporal 时间模型(因此需要temporal-polyfill)。
5. 源码视角:选项同步与模板注入的底层机制
5.1 生命周期编排:创建、diff、恢复、销毁
full-calendar.component.ts 完整实现了AfterViewInit / DoCheck / AfterContentChecked / OnDestroy四个生命周期钩子,形成一条严谨的调度链:
ngAfterViewInit:合并options与独立输入 → 快照初始化 →new Calendar(el, options)→ 渲染;ngDoCheck:在ngOnChanges之前更频繁地被调用,负责对比新旧选项,找出"新增、更新、移除"三类变更,随后调用calendar.pauseRendering()+calendar.resetOptions(newOptions, changedOptionNames)暂停渲染并一次性应用变更;ngAfterContentChecked:调用calendar.resumeRendering()恢复渲染;ngOnDestroy:调用calendar.destroy()释放日历实例。
这一设计保证了对选项对象内部修改(Angular 默认引用比较检测不到)的感知,是"只改options.events数组也能让日历更新"的根本原因。
5.2 deepChangeDetection:深度变更检测开关
组件暴露了@Input() deepChangeDetection?: boolean。当其开启时,对OPTION_IS_DEEP中标记的选项(headerToolbar、footerToolbar、events、eventSources、resources,见 options.ts)使用deepCopy快照 +deepEqual深度比较(工具实现位于 packages/angular/lib/src/utils/fast-deep-equal.ts 与 packages/angular/lib/src/utils/obj.ts)。效果是:即使不替换数组引用,直接events.push(...)或修改某个事件的title字段,日历也能感知并重渲染。
对应的测试位于DeepHostComponent(full-calendar.component.spec.ts):
<full-calendar deepChangeDetection="true" [options]="calendarOptions" ></full-calendar>其中addEventAppend()直接向events数组push新事件,测试断言渲染计数增长;而updateEventTitle()修改相同标题两次时,第二次不会触发重渲染(深度比较发现无变化,渲染计数保持不变),展示了该机制"按内容而非引用"判定变更的特性。需要留意的是,深度拷贝会带来额外开销,仅对数据规模可控的场景推荐开启。
5.3 模板注入:ng-template 如何进入 Preact 渲染树
FullCalendar 核心基于 Preact 渲染,而 Angular 的ng-template由 Angular 编译器管理,二者 DOM 不能直接混用。仓库用两层辅助组件完成了桥接:
- 组件模板内放置一个
offscreen-fragment(实现见 packages/angular/lib/src/utils/offscreen-fragment.component.ts),内部用*ngFor循环customRenderings,逐个渲染transport-container; transport-container(packages/angular/lib/src/utils/transport-container.component.ts)接收containerEl、template、renderProps三个输入,在其ngAfterViewInit/ngOnChanges中调用containerEl.appendChild(wrapperEl),把 Angular 渲染出的无盒包装元素移动到 Preact 为自定义渲染内容预留的容器节点中,从而让 Angular 模板内容"嵌"进日历内部结构。
触发源头在FullCalendarComponent构造函数:它创建CustomRenderingStore,订阅渲染映射(customRenderingMap)并在更新时调用changeDetector.detectChanges();同时把handleCustomRendering作为handleCustomRendering选项、把组件自身作为customRenderingMetaMap(模板名 →TemplateRef的映射)传入Calendar,见buildExtraOptions()(full-calendar.component.ts)。
5.4 getApi():编程式控制日历
组件提供public getApi(): Calendar(full-calendar.component.ts),返回底层Calendar实例,从而支持gotoDate、next、prev、setOption、refetchEvents等全部编程式 API。典型用法是先通过@ViewChild拿到组件引用:
@ViewChild('fullcalendar') fullcalendar?: FullCalendarComponent; someMethod() { this.fullcalendar?.getApi().next(); // 翻到下一段日期范围 this.fullcalendar?.getApi().gotoDate(new Date()); // 跳到指定日期 }该 API 在测试中也得到验证:expect(calendarApi).toBeTruthy()后调用gotoDate并断言getDate()返回同一时刻(full-calendar.component.spec.ts)。
6. 嵌套模板:自定义事件内容渲染
README 还展示了通过ng-template自定义日历局部内容的写法(内容注入,content-injection):
<full-calendar [options]="calendarOptions"> <ng-template #eventContent let-arg> <b>{{arg.timeText}}</b> <i>{{arg.event.title}}</i> </ng-template> </full-calendar>eventContent只是众多可注入模板之一。从 full-calendar.component.ts 可以看到组件通过@ContentChild(..., { static: true })声明了 20 个内容模板槽位,覆盖日历的各个可定制区域:
| 模板名 | 定制内容 |
|---|---|
eventContent | 事件块主体内容 |
dayCellTopContent | 月视图日期格顶部 |
dayHeaderContent | 日期列头 |
allDayHeaderContent | 全天(all-day)区域表头 |
slotHeaderContent | 时间轴(timegrid)列头 |
moreLinkContent | "+2 more" 链接 |
noEventsContent | 列表视图无事件提示 |
nowIndicatorHeaderContent/nowIndicatorLineContent | 当前时间指示器 |
popoverCloseContent | 弹出层关闭按钮 |
weekNumberHeaderContent | 周序号表头 |
listDayHeaderContent | 列表视图日期分组头 |
inlineWeekNumberContent | 行内周序号 |
rowEventBeforeContent/rowEventAfterContent | 事件行前后附加内容 |
resourceCellContent、resourceColumnHeaderContent、resourceDayHeaderContent、resourceExpanderContent、resourceGroupHeaderContent、resourceGroupLaneContent、resourceLaneTopContent、resourceLaneBottomContent | 资源视图(scheduler)相关区域 |
模板上下文类型由 packages/angular/lib/src/private-types.ts 中的CalendarTemplateRef<OptionName>推导:它把TemplateRef的隐式上下文($implicit)绑定为对应 options 回调的首参类型(如eventContent对应eventContent回调的arg对象),因此模板内的arg.event、arg.timeText具有完整类型提示。
测试HostComponentWithTemplate(full-calendar.component.spec.ts)验证了该机制的关键性质:模板内的*ngIf状态变化(isBold)能在不改动 DOM 节点(同一eventEl)的前提下即时切换渲染内容;calendar.next(); calendar.prev()翻页往返后自定义渲染依然保持。另有针对跨视图范围事件的回归测试(DayGridWeekCrossRangeEventHostComponent,issue #8085),确保跨越可视范围的事件在前后翻页后模板内容仍正确显示。
7. 实战参考:仓库内置示例应用
仓库在 packages/angular/app/src/app.component.ts 中提供了一个可直接运行的示例应用(Angular CLI 项目,入口见 packages/angular/app/src/app.module.ts),完整展示了更贴近生产的使用方式:
import { FullCalendarComponent, CalendarOptions, DateClickInfo, EventClickInfo } from '@fullcalendar/angular'; import classicThemePlugin from '@fullcalendar/angular/themes/classic'; import dayGridPlugin from '@fullcalendar/angular/daygrid'; import interactionPlugin, { EventDragStopInfo } from '@fullcalendar/angular/interaction'; export class AppComponent implements OnInit { calendarOptions?: CalendarOptions; @ViewChild('fullcalendar') fullcalendar?: FullCalendarComponent; ngOnInit() { this.calendarOptions = { plugins: [classicThemePlugin, dayGridPlugin, interactionPlugin], editable: true, buttons: { myCustomButton: { text: 'custom!', click: function () { alert('clicked the custom button!'); } } }, headerToolbar: { left: 'prev,next today myCustomButton', center: 'title', right: 'dayGridMonth' }, dateClick: this.handleDateClick.bind(this), eventClick: this.handleEventClick.bind(this), eventDragStop: this.handleEventDragStop.bind(this) }; } handleDateClick(info: DateClickInfo) { console.log(info); } handleEventClick(info: EventClickInfo) { console.log(info); } handleEventDragStop(info: EventDragStopInfo) { console.log(info); } }它示范了几个可迁移的实践:
- 主题插件:
classicThemePlugin来自@fullcalendar/angular/themes/classic(另有monarch、breezy、forma、pulse等主题,构建脚本会为每个主题生成重导出模块); - 自定义工具栏按钮:通过
buttons+headerToolbar注册,点击触发回调; - 交互插件:
interactionPlugin与editable: true组合启用事件拖拽;拖拽结束、日期点击、事件点击回调分别使用EventDragStopInfo、DateClickInfo、EventClickInfo类型(由@fullcalendar/angular的公共 API 导出); - 响应式更新:
updateHeader()直接替换calendarOptions.headerToolbar、updateEvents()直接替换calendarOptions.events(对应模板 packages/angular/app/src/app.component.html 中的两个按钮),在默认变更检测策略下即可生效。
8. 测试验证:组件行为有据可依
仓库用 Karma + Jasmine 对组件进行了较完整的单元/集成测试,测试文件为 packages/angular/lib/src/full-calendar.component.spec.ts,主要覆盖:
- 组件创建与挂载(
should create)、销毁时卸载 DOM(should unmount and call destroy); - 选项变更响应:
weekends: false移除周末类、无关状态变化(something++)不触发视图骨架重渲染(验证 diff 的精准性); events数组整体替换、异步事件函数(successCallback)两种数据源;deepChangeDetection下的数组追加与字段级修改;- 内容模板在翻页、状态切换、跨范围事件下的正确渲染;
- 集成场景:
datesSet中调用setOption修改headerToolbar不抛错(issue #7058)。
这些测试为文章第 5 节描述的各类机制提供了可复现的行为证据,若你在自己的项目中遇到类似场景,可直接参考其写法。
9. 从源码构建与开发脚本
README 末尾给出了仓库本地开发方式:项目采用PNPM管理(本仓库根目录 package.json 与pnpm-workspace.yaml即 pnpm workspace 配置),在仓库根目录执行:
pnpm install然后在packages/angular目录下可用的脚本(见 packages/angular/package.json):
| 脚本 | 作用 |
|---|---|
pnpm run build | 构建生产版 dist(ng build lib→ 运行meta拷贝 README/LICENSE → 执行./scripts/postprocess-dist.ts生成 locales/主题/插件重导出) |
pnpm run watch | 构建并监听开发版 dist |
pnpm run start | 通过ng serve启动示例应用 |
pnpm run test | 使用 ChromeHeadless 无头运行 Karma 测试 |
pnpm run test:dev | 交互式运行测试 |
pnpm run clean | 清理 dist 与 Angular 缓存 |
普通使用者无需关心这些脚本,直接安装 npm 包即可;上述信息有助于理解发布物(dist/lib)中各类.mjs子路径导出文件的来源。
10. 小结
@fullcalendar/angular通过一个full-calendar组件把 FullCalendar 的完整能力接入 Angular 生命周期:[options]绑定 + 独立的events/eventSources/resources输入负责配置与数据,ngDoCheck的选项 diff(配合可选deepChangeDetection)保证变更精确送达,20 个@ContentChild模板槽位实现事件内容与日历各区域的完全自定义,getApi()则开放底层编程式控制。安装时只需npm install @fullcalendar/angular fullcalendar temporal-polyfill并注册FullCalendarModule,7.x 版本适配 Angular 16–22。若需深入阅读实现细节,可从 packages/angular/lib/src/full-calendar.component.ts 出发,结合 packages/angular/lib/src/options.ts 与 packages/angular/scripts/postprocess-dist.ts 理解其设计全貌。
- 前端
- UI组件
【免费下载链接】fullcalendar
Full-sized drag & drop event calendar in JavaScript
相关推荐
在 Angular 中集成 CKEditor 5:官方 Angular 组件安装、配置与实战详解
在 Angular 中集成 CKEditor 5:官方 Angular 组件安装、配置与实战详解 CKEditor 5 提供了官方 Angular 组件 @ck
前端富文本UI组件在 Angular 中集成 Uppy 文件上传组件:@uppy/angular 官方包装器实战指南
在 Angular 中集成 Uppy 文件上传组件:@uppy/angular 官方包装器实战指南 本篇技术指南围绕 Uppy 官方维护的 @uppy/angu
前端UI组件后端推荐使用:FullCalendar Angular 组件
推荐使用:FullCalendar Angular 组件 全功能的事件日程管理工具——FullCalendar 已经推出了官方的 Angular 组件库。这是一
UI库/组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考