MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

常用Angular管道实现日期格式化

2023-05-227.6k 阅读

Angular 管道简介

在 Angular 应用开发中,管道(Pipe)是一种非常强大且实用的功能。管道主要用于对数据进行转换和格式化操作,以便在视图模板中更友好地展示数据。它类似于 Unix 系统中的管道概念,将数据作为输入,经过管道处理后输出转换后的数据。

Angular 提供了许多内置管道,如 UpperCasePipeLowerCasePipeCurrencyPipe 等,同时开发者也可以根据需求创建自定义管道。管道在视图模板中的使用非常简单,通过 | 符号进行数据和管道的连接。例如,要将一个字符串转换为大写,可以这样写:{{ 'hello world' | uppercase }},这样在模板中就会显示 HELLO WORLD

日期格式化需求

在前端开发中,日期的展示是一个常见的需求。不同的应用场景可能需要不同格式的日期展示。比如,在一个新闻应用中,可能需要以 YYYY - MM - DD 的格式展示文章发布日期;在一个日历应用中,可能需要以 MMM DD, YYYY 的格式展示具体日期;而在某些报表应用中,可能还需要展示精确到时分秒的日期格式,如 YYYY - MM - DD HH:MM:SS

如果不进行日期格式化,直接在视图中展示日期对象,通常会得到一个不符合用户阅读习惯的原始日期字符串,例如 Thu Sep 15 2022 10:30:00 GMT+0800 (China Standard Time)。这显然不是我们想要的效果,因此使用管道进行日期格式化就显得尤为重要。

Angular 内置日期管道

Angular 提供了一个内置的日期管道 DatePipe,用于格式化日期。DatePipe 非常强大,可以满足大部分常见的日期格式化需求。

使用方法

在视图模板中使用 DatePipe 很简单,语法如下:{{ dateValue | date : format : timezone }}。其中,dateValue 是要格式化的日期值,可以是一个 Date 对象、时间戳(数字类型)或者符合 ISO 8601 标准的日期字符串;format 是可选参数,用于指定日期的格式;timezone 也是可选参数,用于指定时区。

例如,假设在组件的 TypeScript 代码中有一个日期属性:

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

@Component({
  selector: 'app-date - example',
  templateUrl: './date - example.component.html',
  styleUrls: ['./date - example.component.css']
})
export class DateExampleComponent {
  currentDate = new Date();
}

在模板文件 date - example.component.html 中,可以这样使用 DatePipe

<p>默认格式: {{ currentDate | date }}</p>
<p>自定义格式 YYYY - MM - DD: {{ currentDate | date : 'yyyy - MM - dd' }}</p>
<p>带时分秒的格式 YYYY - MM - DD HH:MM:SS: {{ currentDate | date : 'yyyy - MM - dd HH:mm:ss' }}</p>

在上述代码中,{{ currentDate | date }} 使用的是默认格式,通常会根据用户浏览器的语言和地区设置进行格式化。而 {{ currentDate | date : 'yyyy - MM - dd' }}{{ currentDate | date : 'yyyy - MM - dd HH:mm:ss' }} 则是按照自定义的格式进行日期格式化。

日期格式字符串

DatePipe 的格式字符串有一套特定的规则。常用的占位符如下:

  • y:年。例如,yyyy 表示四位数的年份,yy 表示两位数的年份。
  • M:月。MM 表示两位数的月份(01 - 12),MMM 表示缩写的月份名称(如 Jan, Feb 等),MMMM 表示完整的月份名称(如 January, February 等)。
  • d:日。dd 表示两位数的日期(01 - 31)。
  • h:12 小时制的小时数(01 - 12),HH 表示 24 小时制的小时数(00 - 23)。
  • m:分钟数,mm 表示两位数的分钟数(00 - 59)。
  • s:秒数,ss 表示两位数的秒数(00 - 59)。
  • a:上午/下午标记,会输出 AMPM

例如,'MMM dd, yyyy h:mm:ss a' 这个格式字符串会将日期格式化为类似 Sep 15, 2022 10:30:00 AM 的形式。

时区处理

DatePipe 中的 timezone 参数用于指定时区。如果不提供 timezone 参数,日期将根据用户浏览器的时区设置进行格式化。可以使用以下几种方式指定时区:

  • 时区偏移量:例如,'+0800' 表示东八区。如果要显示的日期是 UTC 时间,而希望在本地时区(假设本地是东八区)显示,可以这样写:{{ utcDate | date : 'yyyy - MM - dd HH:mm:ss' : '+0800' }}
  • 时区名称:一些常见的时区名称也可以使用,如 'America/New_York''Asia/Shanghai' 等。例如,{{ dateValue | date : 'yyyy - MM - dd HH:mm:ss' : 'America/New_York' }} 会将日期按照纽约时区进行格式化。

自定义日期管道

虽然 Angular 的内置 DatePipe 功能强大,但在某些特定的业务场景下,可能需要更定制化的日期格式化逻辑。这时就需要创建自定义日期管道。

创建自定义管道的步骤

  1. 使用 Angular CLI 生成管道:可以使用 Angular CLI 快速生成管道的基本代码结构。在终端中运行以下命令:

    ng generate pipe custom - date
    

    这会在项目中生成一个名为 custom - date.pipe.ts 的文件,内容如下:

    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
      name: 'customDate'
    })
    export class CustomDatePipe implements PipeTransform {
      transform(value: unknown, ...args: unknown[]): unknown {
        return null;
      }
    }
    
  2. 实现 PipeTransform 接口:在 CustomDatePipe 类中,需要实现 PipeTransform 接口的 transform 方法。这个方法接收要处理的数据(在这里是日期值)以及一些可选的参数,返回经过处理后的数据。

    假设我们要实现一个自定义的日期管道,将日期格式化为 星期 X, YYYY - MM - DD 的形式(例如 星期一, 2022 - 09 - 15),代码如下:

    import { Pipe, PipeTransform } from '@angular/core';
    
    const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    
    @Pipe({
      name: 'customDate'
    })
    export class CustomDatePipe implements PipeTransform {
      transform(value: Date | string | number, ...args: unknown[]): string {
        const date = new Date(value);
        const year = date.getFullYear();
        const month = (date.getMonth() + 1).toString().padStart(2, '0');
        const day = date.getDate().toString().padStart(2, '0');
        const weekIndex = date.getDay();
        return `${weekDays[weekIndex]}, ${year}-${month}-${day}`;
      }
    }
    
  3. 在模块中声明管道:生成的自定义管道需要在相关的 Angular 模块中进行声明,才能在应用中使用。打开模块文件(例如 app.module.ts),在 declarations 数组中添加自定义管道:

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform - browser';
    import { AppComponent } from './app.component';
    import { CustomDatePipe } from './custom - date.pipe';
    
    @NgModule({
      declarations: [
        AppComponent,
        CustomDatePipe
      ],
      imports: [
        BrowserModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    
  4. 在视图模板中使用自定义管道:在组件的模板中,可以像使用内置管道一样使用自定义管道。假设在组件中有一个日期属性:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app - custom - date - example',
      templateUrl: './custom - date - example.component.html',
      styleUrls: ['./custom - date - example.component.css']
    })
    export class CustomDateExampleComponent {
      currentDate = new Date();
    }
    

    在模板文件 custom - date - example.component.html 中:

    <p>自定义格式: {{ currentDate | customDate }}</p>
    

处理日期为空或无效的情况

在实际应用中,日期值可能为空或者是无效的日期。对于这种情况,无论是内置的 DatePipe 还是自定义的日期管道,都需要进行适当的处理。

内置 DatePipe 的处理

当使用 DatePipe 时,如果传入的日期值为空或无效,它会返回一个空字符串。例如:

<p>无效日期: {{ 'invalid - date' | date : 'yyyy - MM - dd' }}</p>
<p>空日期: {{ null | date : 'yyyy - MM - dd' }}</p>

在上述代码中,由于 'invalid - date' 不是有效的日期值,null 为空值,因此在模板中会显示空字符串。

自定义管道的处理

在自定义管道中,也需要对空值和无效日期进行处理。以之前的 CustomDatePipe 为例,可以添加如下处理逻辑:

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

const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];

@Pipe({
  name: 'customDate'
})
export class CustomDatePipe implements PipeTransform {
  transform(value: Date | string | number, ...args: unknown[]): string {
    if (!value) {
      return '';
    }
    const date = new Date(value);
    if (isNaN(date.getTime())) {
      return '';
    }
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    const weekIndex = date.getDay();
    return `${weekDays[weekIndex]}, ${year}-${month}-${day}`;
  }
}

在上述代码中,首先检查 value 是否为空,如果为空则直接返回空字符串。然后创建 Date 对象后,通过 isNaN(date.getTime()) 检查日期是否有效,如果无效也返回空字符串。这样可以确保在遇到空值或无效日期时,自定义管道有合理的输出。

国际化与日期格式化

在全球化的应用开发中,日期的格式化需要考虑不同地区和语言的习惯。Angular 的 DatePipe 在这方面提供了很好的支持。

根据浏览器语言设置格式化

当不指定日期格式字符串时,DatePipe 会根据用户浏览器的语言和地区设置进行日期格式化。例如,在英语环境的浏览器中,默认格式可能是类似 Sep 15, 2022 的形式;而在中文环境的浏览器中,默认格式可能是 2022年9月15日

显式设置语言环境

可以通过在模块中配置 LOCALE_ID 来显式设置应用的语言环境。首先,安装 @angular/localize 包:

npm install @angular/localize

然后,在 app.module.ts 中导入相关模块并配置 LOCALE_ID

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { AppComponent } from './app.component';
import { registerLocaleData } from '@angular/common';
import localeEn from '@angular/common/locales/en';
import localeZh from '@angular/common/locales/zh';

registerLocaleData(localeEn);
registerLocaleData(localeZh);

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'zh' } // 设置为中文环境
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

在上述代码中,通过 registerLocaleData 注册了英语和中文的语言环境数据,然后通过 providers 中的 LOCALE_ID 设置应用的语言环境为中文。这样,DatePipe 在使用默认格式时会按照中文的日期格式习惯进行格式化。

如果要在不同语言环境之间切换,可以在运行时动态更改 LOCALE_ID 的值。例如,可以在服务中提供一个方法来切换语言环境:

import { Injectable } from '@angular/core';
import { LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeEn from '@angular/common/locales/en';
import localeZh from '@angular/common/locales/zh';

@Injectable({
  providedIn: 'root'
})
export class LocaleService {
  constructor() {
    registerLocaleData(localeEn);
    registerLocaleData(localeZh);
  }

  setLocale(locale: string) {
    if (locale === 'en') {
      this.setLocaleValue('en');
    } else if (locale === 'zh') {
      this.setLocaleValue('zh');
    }
  }

  private setLocaleValue(locale: string) {
    const localeValue = { provide: LOCALE_ID, useValue: locale };
    const injector = this.getInjector();
    injector.get(LOCALE_ID, null, { optional: true, host: true, skipSelf: true })?.provider.deactivate(injector);
    injector.get(LOCALE_ID, null, { optional: true, host: true, skipSelf: true })?.provider.ngOnDestroy?.call(null, injector);
    injector.addProvider(localeValue);
  }

  private getInjector() {
    let current = this;
    while (current &&!(<any>current).injector) {
      current = (<any>current).__parent;
    }
    return (<any>current).injector;
  }
}

在组件中,可以通过注入 LocaleService 来切换语言环境:

import { Component } from '@angular/core';
import { LocaleService } from './locale.service';

@Component({
  selector: 'app - locale - switch',
  templateUrl: './locale - switch.component.html',
  styleUrls: ['./locale - switch.component.css']
})
export class LocaleSwitchComponent {
  constructor(private localeService: LocaleService) {}

  switchToEnglish() {
    this.localeService.setLocale('en');
  }

  switchToChinese() {
    this.localeService.setLocale('zh');
  }
}

在模板文件 locale - switch.component.html 中添加切换按钮:

<button (click)="switchToEnglish()">切换到英语</button>
<button (click)="switchToChinese()">切换到中文</button>
<p>当前日期: {{ currentDate | date }}</p>

这样,当点击按钮切换语言环境时,DatePipe 的默认日期格式会相应地根据所选语言环境进行变化。

性能优化与日期管道

在应用中频繁使用日期管道进行格式化操作时,性能可能会成为一个问题。以下是一些性能优化的建议。

避免不必要的管道使用

尽量在数据进入视图之前就进行必要的日期格式化处理,而不是在视图模板中每次都通过管道进行格式化。例如,如果在组件的业务逻辑中已经确定了特定的日期格式需求,可以在组件的 TypeScript 代码中进行格式化,然后将格式化后的字符串传递给视图。

缓存管道结果

对于一些不经常变化的日期值,可以考虑缓存管道的格式化结果。例如,如果在一个组件中有一个固定的日期值用于展示,并且在组件的生命周期内不会改变,可以在组件初始化时使用管道进行一次格式化,并将结果缓存起来,而不是每次视图更新时都重新调用管道。

使用 OnPush 策略

如果组件的输入数据只有日期值,并且日期值不经常变化,可以将组件的 changeDetection 策略设置为 OnPush。这样,只有当输入的日期值发生引用变化时,才会触发组件的更新和管道的重新执行,从而提高性能。在组件的元数据中设置 changeDetection 策略如下:

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

@Component({
  selector: 'app - date - performance - example',
  templateUrl: './date - performance - example.component.html',
  styleUrls: ['./date - performance - example.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DatePerformanceExampleComponent {
  currentDate = new Date();
}

与其他库结合使用

在实际项目中,可能会使用一些第三方的日期处理库,如 moment.jsday - js,与 Angular 的日期管道结合使用,以获得更强大的日期处理和格式化功能。

与 moment.js 结合

moment.js 曾经是一个非常流行的 JavaScript 日期处理库,虽然它现在已经不再推荐用于新项目,但在一些旧项目中可能还会使用。要与 Angular 结合使用,可以在自定义管道中使用 moment.js 的功能。

首先,安装 moment.js

npm install moment

然后,创建一个使用 moment.js 的自定义日期管道。例如,要实现一个将日期格式化为 MMM DD YYYY [at] HH:mm 格式(如 Sep 15 2022 at 10:30)的管道:

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

@Pipe({
  name:'momentDate'
})
export class MomentDatePipe implements PipeTransform {
  transform(value: Date | string | number, ...args: unknown[]): string {
    const momentDate = moment(value);
    if (!momentDate.isValid()) {
      return '';
    }
    return momentDate.format('MMM DD YYYY [at] HH:mm');
  }
}

在模块中声明该管道,并在视图模板中使用:

<p>使用 moment.js 格式化: {{ currentDate | momentDate }}</p>

与 day - js 结合

day - js 是一个轻量级的日期处理库,在现代项目中越来越受欢迎。同样,先安装 day - js

npm install day - js

创建使用 day - js 的自定义日期管道。例如,实现一个将日期格式化为 dddd, MMMM D, YYYY 格式(如 Thursday, September 15, 2022)的管道:

import { Pipe, PipeTransform } from '@angular/core';
import dayjs from 'day - js';

@Pipe({
  name: 'dayjsDate'
})
export class DayjsDatePipe implements PipeTransform {
  transform(value: Date | string | number, ...args: unknown[]): string {
    const dayjsDate = dayjs(value);
    if (!dayjsDate.isValid()) {
      return '';
    }
    return dayjsDate.format('dddd, MMMM D, YYYY');
  }
}

在模块中声明管道并在视图模板中使用:

<p>使用 day - js 格式化: {{ currentDate | dayjsDate }}</p>

通过与这些第三方库结合,可以利用它们丰富的日期处理和格式化功能,扩展 Angular 日期管道的能力,满足更复杂的业务需求。同时,在结合使用时要注意库的版本兼容性以及性能问题,确保应用的稳定性和高效性。

通过以上对 Angular 中常用日期管道的介绍、自定义管道的实现、日期为空或无效的处理、国际化、性能优化以及与其他库结合使用等方面的内容,相信开发者能够在前端开发中更好地处理日期格式化相关的需求,为用户提供更友好、准确的日期展示。无论是使用内置的 DatePipe 还是创建自定义管道,都需要根据具体的业务场景和需求来选择最合适的方式,以确保应用的质量和用户体验。在实际项目中,还需要不断测试和优化日期格式化的功能,以适应不同的输入情况和用户需求。同时,随着 Angular 框架的不断发展和第三方日期处理库的更新,开发者也需要持续关注相关技术的变化,及时调整和优化代码,以保持应用的先进性和兼容性。在处理日期相关功能时,还需要特别注意时区、闰年、月份天数等细节问题,避免出现日期计算和格式化错误。通过严谨的代码实现和充分的测试,能够打造出健壮可靠的日期格式化功能,为整个 Angular 应用增色不少。在多人协作开发项目中,对于日期格式化的规则和实现方式应该有明确的文档说明,以便团队成员能够统一理解和遵循,减少因不一致导致的问题。总之,日期格式化虽然看似是一个小功能,但在前端应用中却非常重要,需要开发者认真对待和精心处理。