Использование с Angular

Flatpickr представляет собой лёгкий и гибкий datepicker, который легко интегрируется в экосистему Angular благодаря отсутствию жёсткой привязки к DOM-структуре и минимальным требованиям к окружению. Основная задача интеграции — корректно синхронизировать жизненный цикл компонента Angular с инициализацией и уничтожением экземпляра Flatpickr.

Установка выполняется через npm:

npm install flatpickr

Дополнительно часто требуется типизация:

npm install --save-dev @types/flatpickr

Импорт базовых стилей:

import 'flatpickr/dist/flatpickr.min.css';

Базовая инициализация внутри компонента

Простейший вариант интеграции строится через ViewChild и прямую инициализацию после рендера шаблона.

import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import flatpickr, { Instance } from 'flatpickr';

@Component({
  selector: 'app-datepicker',
  template: `<input #input type="text" />`
})
export class DatepickerComponent implements AfterViewInit, OnDestroy {
  @ViewChild('input', { static: false }) input!: ElementRef<HTMLInputElement>;

  private fp!: Instance;

  ngAfterViewInit(): void {
    this.fp = flatpickr(this.input.nativeElement, {
      dateFormat: 'Y-m-d',
      enableTime: false
    });
  }

  ngOnDestroy(): void {
    this.fp?.destroy();
  }
}

Ключевой момент заключается в том, что инициализация возможна только после появления DOM-элемента, поэтому используется AfterViewInit.


Связывание с Angular Forms (Reactive Forms)

Интеграция с реактивными формами требует синхронизации состояния Flatpickr с FormControl.

import { Component, AfterViewInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import flatpickr, { Instance } from 'flatpickr';

@Component({
  selector: 'app-datepicker',
  template: `<input #input type="text" />`
})
export class DatepickerComponent implements AfterViewInit, OnDestroy {
  @ViewChild('input') input!: ElementRef<HTMLInputElement>;

  control = new FormControl<Date | string | null>(null);

  private fp!: Instance;

  ngAfterViewInit(): void {
    this.fp = flatpickr(this.input.nativeElement, {
      onChange: (selectedDates) => {
        this.control.setValue(selectedDates[0] ?? null);
      }
    });

    this.control.valueChanges.subscribe(value => {
      if (value) {
        this.fp.setDate(value as Date, false);
      }
    });
  }

  ngOnDestroy(): void {
    this.fp?.destroy();
  }
}

Синхронизация двусторонняя:

  • Flatpickr → FormControl через onChange
  • FormControl → Flatpickr через setDate

Инкапсуляция через Angular Directive

Более архитектурно корректный подход заключается в создании директивы, которая превращает любой input в календарь.

import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
import flatpickr, { Instance, Options } from 'flatpickr';

@Directive({
  selector: '[appFlatpickr]'
})
export class FlatpickrDirective implements OnInit, OnDestroy {
  @Input('appFlatpickr') options: Options = {};

  private fp!: Instance;

  constructor(private el: ElementRef<HTMLInputElement>) {}

  ngOnInit(): void {
    this.fp = flatpickr(this.el.nativeElement, this.options);
  }

  ngOnDestroy(): void {
    this.fp?.destroy();
  }
}

Использование:

<input [appFlatpickr]="{ dateFormat: 'Y-m-d' }" />

Такой подход исключает дублирование логики и упрощает масштабирование.


Обновление конфигурации динамически

Flatpickr не является полностью реактивным, поэтому изменение опций требует пересоздания экземпляра.

setOptions(newOptions: any): void {
  this.fp?.destroy();
  this.fp = flatpickr(this.input.nativeElement, newOptions);
}

При использовании в Angular важно учитывать, что пересоздание должно быть синхронизировано с ChangeDetectionStrategy, чтобы избежать лишних перерендеров.


Работа с форматами дат и локализацией

Flatpickr поддерживает кастомные локали и форматирование через date-fns-подобный синтаксис.

import { Russian } from 'flatpickr/dist/l10n/ru';

this.fp = flatpickr(this.input.nativeElement, {
  locale: Russian,
  dateFormat: 'd.m.Y'
});

Для Angular-приложений с интернационализацией часто используется динамическая подстановка локали:

@Input() locale: any;

ngOnChanges(): void {
  if (this.fp && this.locale) {
    this.fp.set('locale', this.locale);
  }
}

Поддержка TimePicker и комбинированных режимов

Flatpickr позволяет объединять дату и время:

this.fp = flatpickr(this.input.nativeElement, {
  enableTime: true,
  dateFormat: 'Y-m-d H:i',
  time_24hr: true
});

В Angular это часто связывается с FormControl<string> вместо Date, чтобы избежать проблем сериализации.


Управление состоянием и жизненным циклом

Angular повторно создает DOM-элементы при условных рендерах (*ngIf), что приводит к необходимости защиты от утечек памяти:

  • уничтожение экземпляра в ngOnDestroy
  • повторная инициализация при повторном создании компонента
  • предотвращение двойной инициализации
if (this.fp) {
  this.fp.destroy();
}

Также важно учитывать, что повторная инициализация без уничтожения старого экземпляра приводит к наложению обработчиков событий.


Асинхронная загрузка и lazy initialization

В крупных Angular-приложениях Flatpickr часто загружается лениво:

async ngAfterViewInit() {
  const flatpickrModule = await import('flatpickr');
  this.fp = flatpickrModule.default(this.input.nativeElement, {});
}

Такой подход уменьшает начальный bundle и ускоряет first paint.


Интеграция с кастомными темами Angular

Flatpickr использует собственные CSS-классы, которые можно переопределять через глобальные стили Angular:

.flatpickr-calendar {
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}

При использовании ViewEncapsulation важно размещать стили в styles.scss, иначе инкапсуляция может блокировать переопределение.


Поддержка серверного рендеринга (Angular SSR)

В среде SSR доступ к window отсутствует, поэтому инициализация должна быть защищена:

import { isPlatformBrowser } from '@angular/common';
import { Inject, PLATFORM_ID } from '@angular/core';

constructor(@Inject(PLATFORM_ID) private platformId: object) {}

ngAfterViewInit(): void {
  if (isPlatformBrowser(this.platformId)) {
    this.fp = flatpickr(this.input.nativeElement, {});
  }
}

Flatpickr должен запускаться только в браузере, иначе SSR-процесс может упасть.


Интеграция с кастомными плагинами Flatpickr

Flatpickr поддерживает плагины, которые можно подключать в Angular через конфигурацию:

import weekSelect from 'flatpickr/dist/plugins/weekSelect/weekSelect';

this.fp = flatpickr(this.input.nativeElement, {
  plugins: [weekSelect()]
});

При архитектуре Angular-плагинов часто создаётся фабрика конфигураций:

export function createDatepickerOptions() {
  return {
    dateFormat: 'Y-m-d',
    plugins: []
  };
}

Событийная модель и Angular обработчики

Flatpickr предоставляет набор событий:

this.fp = flatpickr(this.input.nativeElement, {
  onOpen: () => console.log('open'),
  onClose: () => console.log('close'),
  onChange: (dates) => console.log(dates)
});

В Angular эти события обычно преобразуются в @Output:

@Output() change = new EventEmitter<Date[]>();

onChange: (dates) => this.change.emit(dates)

Оптимизация производительности

Основные источники нагрузки:

  • повторная инициализация календаря
  • частые изменения FormControl
  • отсутствие OnPush стратегии

Оптимизация включает:

  • ChangeDetectionStrategy.OnPush
  • мемоизацию конфигурации
  • использование директив вместо компонентов-обёрток
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

Типизация и строгая интеграция

TypeScript-интеграция позволяет избежать ошибок при конфигурации:

import { Options } from 'flatpickr';

const options: Options = {
  enableTime: true,
  dateFormat: 'Y-m-d'
};

Использование строгих типов особенно важно при динамической генерации конфигураций в Angular сервисах.