Angular и pipes

В Angular преобразование данных для отображения выполняется через механизм pipes. Pipes представляют собой чистые функции, применяемые в шаблонах для форматирования, фильтрации и трансформации значений без изменения исходных данных. При работе с датами стандартные средства JavaScript оказываются ограниченными, особенно в задачах локализации, относительного времени и сложного форматирования. В этих сценариях часто применяется библиотека Day.js.

Day.js представляет собой компактную библиотеку для работы с датами, построенную с учётом API Moment.js, но с минимальным размером и модульной архитектурой. Она легко интегрируется в Angular через пользовательские pipes, позволяя централизовать логику форматирования дат.


Архитектура pipe для дат

Pipe в Angular представляет собой класс с методом transform, который принимает входное значение и возвращает преобразованный результат. Для дат это значение обычно приходит в виде строки, timestamp или объекта Date.

Базовая структура pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'formatDate'
})
export class FormatDatePipe implements PipeTransform {
  transform(value: any): string {
    return value;
  }
}

Такой pipe не выполняет преобразований, но задаёт основу для подключения Day.js.


Подключение Day.js в Angular

Day.js устанавливается через npm:

npm install dayjs

Далее библиотека импортируется в pipe:

import * as dayjs from 'dayjs';

Для расширения функциональности подключаются плагины. Например, для работы с относительным временем:

npm install dayjs-plugin-relativeTime

Или встроенный плагин:

import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);

Форматирование дат через pipe

Основная задача pipe — преобразование даты в читаемый формат.

import { Pipe, PipeTransform } from '@angular/core';
import * as dayjs from 'dayjs';

@Pipe({
  name: 'formatDate'
})
export class FormatDatePipe implements PipeTransform {
  transform(value: string | Date, format: string = 'DD.MM.YYYY'): string {
    if (!value) {
      return '';
    }

    return dayjs(value).format(format);
  }
}

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

<span>{{ user.createdAt | formatDate:'YYYY-MM-DD' }}</span>

Day.js поддерживает гибкие шаблоны форматирования, включая часы, минуты, секунды, а также локализованные представления дат.


Относительное время

Одной из ключевых возможностей Day.js является отображение времени относительно текущего момента.

Pipe для relative time:

import { Pipe, PipeTransform } from '@angular/core';
import * as dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';

dayjs.extend(relativeTime);

@Pipe({
  name: 'fromNow'
})
export class FromNowPipe implements PipeTransform {
  transform(value: string | Date): string {
    if (!value) {
      return '';
    }

    return dayjs(value).fromNow();
  }
}

Применение:

<span>{{ comment.createdAt | fromNow }}</span>

Результат: «2 hours ago», «5 days ago» или локализованный аналог при подключении языка.


Локализация дат

Day.js поддерживает подключение языковых пакетов.

import * as dayjs from 'dayjs';
import 'dayjs/locale/ru';

dayjs.locale('ru');

После этого все методы форматирования и relative time автоматически используют выбранную локаль.

Pipe с учётом локали:

import { Pipe, PipeTransform } from '@angular/core';
import * as dayjs from 'dayjs';

@Pipe({
  name: 'localizedDate'
})
export class LocalizedDatePipe implements PipeTransform {
  transform(value: string | Date): string {
    if (!value) {
      return '';
    }

    return dayjs(value).format('D MMMM YYYY');
  }
}

Универсальный pipe с режимами форматирования

В реальных приложениях часто требуется единый pipe, поддерживающий несколько режимов: форматирование, относительное время и кастомные шаблоны.

import { Pipe, PipeTransform } from '@angular/core';
import * as dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';

dayjs.extend(relativeTime);

@Pipe({
  name: 'dayjsFormat'
})
export class DayjsFormatPipe implements PipeTransform {
  transform(
    value: string | Date,
    mode: 'format' | 'fromNow' = 'format',
    formatStr: string = 'DD.MM.YYYY'
  ): string {
    if (!value) {
      return '';
    }

    const date = dayjs(value);

    switch (mode) {
      case 'fromNow':
        return date.fromNow();
      case 'format':
      default:
        return date.format(formatStr);
    }
  }
}

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

<span>{{ task.deadline | dayjsFormat:'format':'YYYY-MM-DD HH:mm' }}</span>
<span>{{ task.updatedAt | dayjsFormat:'fromNow' }}</span>

Оптимизация работы pipes с датами

Angular pipes могут вызываться часто при изменении детекции. Day.js сам по себе лёгкий, но при большом количестве элементов важно учитывать производительность.

Применяются следующие подходы:

  • использование pure pipes (по умолчанию)
  • исключение создания новых объектов внутри transform без необходимости
  • минимизация цепочек преобразований
  • предварительная нормализация данных в сервисах

Пример чистого pipe:

@Pipe({
  name: 'formatDate',
  pure: true
})

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

Day.js поддерживает расширения для работы с UTC и временными зонами.

npm install dayjs-plugin-utc dayjs-plugin-timezone

Подключение:

import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(timezone);

Pipe с учётом временной зоны:

@Pipe({
  name: 'tzDate'
})
export class TimezoneDatePipe implements PipeTransform {
  transform(value: string | Date, tz: string = 'UTC'): string {
    if (!value) {
      return '';
    }

    return dayjs(value).tz(tz).format('DD.MM.YYYY HH:mm');
  }
}

Использование Day.js в сервисах вместо pipe

В Angular архитектуре форматирование дат может выполняться не только через pipes, но и через сервисы, особенно если результат используется в нескольких местах.

import { Injectable } from '@angular/core';
import * as dayjs from 'dayjs';

@Injectable({
  providedIn: 'root'
})
export class DateService {
  format(value: string | Date, format: string): string {
    return dayjs(value).format(format);
  }

  fromNow(value: string | Date): string {
    return dayjs(value).fromNow();
  }
}

Pipe в таком случае становится тонкой обёрткой над сервисом, что улучшает тестируемость и переиспользование логики.


Интеграция с реактивными данными

При работе с Observable в Angular даты часто приходят асинхронно. Pipes с Day.js остаются применимыми без дополнительной логики:

<div *ngIf="user$ | async as user">
  {{ user.lastLogin | fromNow }}
</div>

Day.js выполняет преобразование синхронно, не требуя дополнительных подписок или преобразований потоков данных.


Стандартизация форматов в приложении

В крупных приложениях применяются единые константы форматов:

export const DATE_FORMATS = {
  short: 'DD.MM.YY',
  long: 'DD MMMM YYYY',
  time: 'HH:mm',
  full: 'DD.MM.YYYY HH:mm:ss'
};

Pipe использует эти значения:

transform(value: string | Date, format: string = DATE_FORMATS.short): string {
  return dayjs(value).format(format);
}

Это снижает дублирование строк форматов и упрощает поддержку.


Обработка некорректных дат

Day.js возвращает объект invalid date при некорректном вводе. В pipe важно учитывать это поведение:

transform(value: string | Date): string {
  const date = dayjs(value);

  if (!date.isValid()) {
    return '';
  }

  return date.format('DD.MM.YYYY');
}

Комбинирование нескольких плагинов

Day.js позволяет расширять функциональность через плагины, что особенно полезно в Angular pipe-архитектуре:

  • relativeTime — относительное время
  • utc — UTC преобразования
  • timezone — временные зоны
  • advancedFormat — расширенные форматы

Пример инициализации:

import * as dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(relativeTime);
dayjs.extend(utc);
dayjs.extend(timezone);