Интеграция с Angular

Angular использует компонентную архитектуру, механизм обнаружения изменений (Change Detection), двустороннее связывание данных и систему жизненного цикла компонентов. Библиотека Pikaday создавалась как независимый JavaScript-компонент без привязки к какому-либо фреймворку, поэтому для корректной интеграции требуется учитывать особенности взаимодействия между Angular и внешним DOM-виджетом.

Основная идея заключается в том, что Angular управляет состоянием приложения, а Pikaday отвечает исключительно за визуальный выбор даты.

Наиболее распространённые сценарии интеграции:

  • использование внутри отдельного компонента;
  • работа через Reactive Forms;
  • работа через Template-driven Forms;
  • создание собственной Angular-обёртки;
  • интеграция с сервисами и состоянием приложения;
  • динамическое создание и уничтожение календаря.

Установка зависимостей

Pikaday устанавливается через npm:

npm install pikaday

При использовании TypeScript может потребоваться установка типизации:

npm install --save-dev @types/pikaday

Импорт в компоненте:

import Pikaday from 'pikaday';

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

HTML-шаблон:

<input #dateInput type="text">

Компонент:

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

import Pikaday from 'pikaday';

@Component({
  selector: 'app-datepicker',
  templateUrl: './datepicker.component.html'
})
export class DatepickerComponent implements AfterViewInit {

  @ViewChild('dateInput')
  dateInput!: ElementRef<HTMLInputElement>;

  private picker!: Pikaday;

  ngAfterViewInit(): void {

    this.picker = new Pikaday({
      field: this.dateInput.nativeElement
    });

  }
}

Почему используется ngAfterViewInit

На момент выполнения конструктора компонента элемент <input> ещё отсутствует в DOM.

Хук жизненного цикла:

ngAfterViewInit()

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


Освобождение ресурсов

Каждый экземпляр Pikaday создаёт собственные обработчики событий.

При удалении компонента календарь желательно уничтожать.

import {
  Component,
  OnDestroy
} from '@angular/core';

export class DatepickerComponent implements OnDestroy {

  private picker!: Pikaday;

  ngOnDestroy(): void {

    if (this.picker) {
      this.picker.destroy();
    }

  }
}

Это предотвращает:

  • утечки памяти;
  • накопление обработчиков событий;
  • проблемы при повторном создании компонента.

Получение выбранной даты

Pikaday предоставляет обработчик onSelect.

this.picker = new Pikaday({
  field: this.dateInput.nativeElement,

  onSelect: (date: Date) => {

    console.log(date);

  }
});

После выбора даты в консоль будет выведен объект Date.

Например:

Tue Jun 02 2026 00:00:00 GMT+0500

Связывание с состоянием компонента

Обычно дата хранится в свойстве класса.

selectedDate: Date | null = null;

Инициализация:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  onSelect: (date: Date) => {

    this.selectedDate = date;

  }

});

Теперь выбранная дата становится частью состояния Angular-компонента.


Отображение выбранной даты

Шаблон:

<input #dateInput>

<p>
  {{ selectedDate | date:'dd.MM.yyyy' }}
</p>

Компонент:

selectedDate: Date | null = null;

После выбора даты Angular автоматически обновит отображение.


Работа с Change Detection

Иногда внешний JavaScript-код изменяет данные вне зоны Angular.

Для гарантированного обновления интерфейса можно использовать NgZone.

import { NgZone } from '@angular/core';

Конструктор:

constructor(
  private zone: NgZone
) {}

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

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  onSelect: (date) => {

    this.zone.run(() => {

      this.selectedDate = date;

    });

  }

});

Так Angular точно узнает об изменении состояния.


Интеграция с Reactive Forms

Reactive Forms является наиболее популярным способом работы с формами в Angular.

Создание формы:

import {
  FormControl,
  FormGroup
} from '@angular/forms';

form = new FormGroup({
  date: new FormControl('')
});

HTML:

<form [formGroup]="form">

  <input
    #dateInput
    formControlName="date">

</form>

Создание календаря:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  onSelect: (date: Date) => {

    this.form.get('date')?.setValue(date);

  }

});

Теперь выбранная дата автоматически попадает в форму.


Чтение данных Reactive Form

Получение значения:

const value = this.form.value;

Результат:

{
  date: Tue Jun 02 2026
}

Отслеживание изменений:

this.form.get('date')
  ?.valueChanges
  .subscribe(value => {

    console.log(value);

  });

Все изменения Pikaday будут поступать через поток формы.


Установка даты из Reactive Form

Часто дата приходит с сервера.

this.form.patchValue({
  date: new Date('2026-06-15')
});

После обновления формы следует синхронизировать календарь:

const date = this.form.get('date')?.value;

if (date) {
  this.picker.setDate(date);
}

Двусторонняя синхронизация

Полноценная интеграция требует передачи данных в обе стороны.

Из Pikaday в Angular:

onSelect: (date) => {
  this.form.get('date')?.setValue(date);
}

Из Angular в Pikaday:

this.form.get('date')
  ?.valueChanges
  .subscribe(date => {

    if (date) {
      this.picker.setDate(date);
    }

  });

Так состояние формы и календаря остаются согласованными.


Использование с Template-driven Forms

Компонент:

selectedDate!: Date;

HTML:

<input
  #dateInput
  [(ngModel)]="selectedDate"
  name="date">

Настройка:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  onSelect: (date) => {

    this.selectedDate = date;

  }

});

Такой подход работает, однако Reactive Forms обычно предоставляет больше возможностей и лучше подходит для крупных приложений.


Ограничение диапазона дат

Минимальная дата:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  minDate: new Date()

});

Максимальная дата:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  maxDate: new Date(2027, 11, 31)

});

Диапазон:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  minDate: new Date(2026, 0, 1),

  maxDate: new Date(2026, 11, 31)

});

Локализация

Настройка русской локали:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  i18n: {

    previousMonth: 'Предыдущий',

    nextMonth: 'Следующий',

    months: [
      'Январь',
      'Февраль',
      'Март',
      'Апрель',
      'Май',
      'Июнь',
      'Июль',
      'Август',
      'Сентябрь',
      'Октябрь',
      'Ноябрь',
      'Декабрь'
    ],

    weekdays: [
      'Воскресенье',
      'Понедельник',
      'Вторник',
      'Среда',
      'Четверг',
      'Пятница',
      'Суббота'
    ],

    weekdaysShort: [
      'Вс',
      'Пн',
      'Вт',
      'Ср',
      'Чт',
      'Пт',
      'Сб'
    ]

  }

});

Форматирование даты

Для отображения даты в нужном формате:

this.picker = new Pikaday({

  field: this.dateInput.nativeElement,

  format: 'DD.MM.YYYY'

});

Пример результата:

02.06.2026

Создание переиспользуемого Angular-компонента

Вместо дублирования логики удобно создать обёртку.

Шаблон:

<input #input>

Компонент:

@Component({
  selector: 'app-pikaday',
  templateUrl: './pikaday.component.html'
})
export class PikadayComponent {}

После этого календарь используется так:

<app-pikaday></app-pikaday>

Подобная инкапсуляция упрощает поддержку проекта.


Передача данных через @Input

@Input()
minDate!: Date;

@Input()
maxDate!: Date;

Создание календаря:

this.picker = new Pikaday({

  field: this.input.nativeElement,

  minDate: this.minDate,

  maxDate: this.maxDate

});

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

<app-pikaday
  [minDate]="startDate"
  [maxDate]="endDate">
</app-pikaday>

Передача выбранной даты через @Output

@Output()
dateChange = new EventEmitter<Date>();

Выбор даты:

onSelect: (date) => {

  this.dateChange.emit(date);

}

Родительский компонент:

<app-pikaday
  (dateChange)="onDateSelected($event)">
</app-pikaday>

Реализация ControlValueAccessor

Для полной поддержки Angular Forms пользовательский компонент должен реализовывать интерфейс ControlValueAccessor.

Основные методы:

writeValue(value: any): void

registerOnChange(fn: any): void

registerOnTouched(fn: any): void

setDisabledState(isDisabled: boolean): void

Такой подход позволяет использовать компонент аналогично стандартным элементам формы:

<app-pikaday
  formControlName="date">
</app-pikaday>

Преимущества:

  • совместимость с Reactive Forms;
  • поддержка валидаторов;
  • единый API форм Angular;
  • переиспользуемость компонента.

Динамическое создание календаря

Календарь может создаваться только при необходимости.

openCalendar(): void {

  if (!this.picker) {

    this.picker = new Pikaday({
      field: this.dateInput.nativeElement
    });

  }

}

Подход полезен при большом количестве полей даты на странице.


Работа с серверными данными

Получение даты через HTTP:

this.http
  .get('/api/user')
  .subscribe((data: any) => {

    const date = new Date(data.birthDate);

    this.picker.setDate(date);

  });

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


Типичные проблемы интеграции

Календарь не появляется

Наиболее распространённые причины:

  • отсутствует вызов ngAfterViewInit;
  • ViewChild не найден;
  • элемент скрыт через *ngIf;
  • экземпляр Pikaday создаётся раньше появления DOM.

Не обновляется шаблон Angular

Причина часто связана с работой вне Angular Zone.

Решение:

this.zone.run(() => {
  this.selectedDate = date;
});

Возникают утечки памяти

Причина:

new Pikaday(...)

создаётся многократно без уничтожения предыдущих экземпляров.

Решение:

this.picker.destroy();

в ngOnDestroy.

Reactive Form не получает данные

Необходимо явно обновлять контрол:

this.form.get('date')
  ?.setValue(date);

Pikaday сам по себе не знает о существовании формы Angular.


Архитектура промышленного решения

Для крупных приложений наиболее надёжной считается схема:

  1. Создание отдельного Angular-компонента-обёртки.
  2. Реализация ControlValueAccessor.
  3. Поддержка Input и Output параметров.
  4. Автоматическое уничтожение экземпляра через OnDestroy.
  5. Использование Reactive Forms.
  6. Инкапсуляция всей логики Pikaday внутри компонента.

Такой подход обеспечивает полную совместимость с инфраструктурой Angular, позволяет централизованно управлять настройками календаря и значительно упрощает дальнейшее сопровождение приложения.