Angular 复习与进阶系列 – Component 组件 の Pipe

发布时间 2023-04-06 16:57:15作者: 兴杰

介绍

Pipe 类似于 Template Syntax, 它的用途是 transform value for display.

 

参考: 

Docs – Understanding Pipes

 

DatePipe

一个简单的例子,

我有一个 JavaScript 的 Date value, 我要 display 给用户看. 你不可能直接 .toString() 就显示嘛. 很丑.

所以呢, 它就需要一个 transform. 而按照职责划分 (Thinking in Angular Way), 这个任务显然是属于 view 的 logic

所以应该在 HTML 去表达. 于是就有了 Pipe 写法

<h1>{{ today }}</h1>
<h1>{{ today | date : 'dd MMM yyyy' }}</h1>
<h1>{{ today | date : 'fullDate' }}</h1>

pipe 的语法是这样的 {{  value | pipeName : paramter1 : paramter2  }}

你可以把它看作是一个 function call, 

比如现在我们使用的 DatePipe, 就是 const displayDate = date(today , 'dd MMM yyyy');

today 是 component property, 是一个 new Date()

| pipe 就是启动 pipe transform

date 是 Angular build-in 的 DatePipe, Angular build-in 了许多 pipe. 每一个负责不同的 transform. 顾名思义 DatePipe 自然是用于 transform date value.

注: 要使用 Angular build-in 的 Pipe, 必须在 Component metadata imports CommonModule

: 分号表示要输入 paramters

'dd MMM yyyy' 则是输入的 paramter, 声明想 transform to 什么 date format. (p.s. 想知道所有 Angular 支持的 format, 参考这里)

效果

 

UpperCasePipe, LowerCasePipe, TitleCase

<h1>{{ 'Hello World' | uppercase }}</h1>
<h1>{{ 'Hello World' | lowercase }}</h1>
<h1>{{ 'hello world' | titlecase }}</h1>

效果

 

DecimalPipe

在 JavaScript, 我们可以用下划线来分割 number 让它好看一点

<h1>{{ 1_000_000 }}</h1>

但当渲染 HTML 的时候, 它会被 number.toString(), 结果变成

DecimalPipe 可用于解决这种问题

<h1>{{ 1_000_000 | number }}</h1>

注: 虽然它叫 DecimalPipe, 但使用时是 | number 哦

效果

除此之外, number 还支持一个 digit setting

它可以控制小数点前后号码, 最少最多几个数字

语法是

{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}

for example '2-2-4' 表示小数点前最少要 2 digit, 小数点后最少 2 最多 4 digit.

不够 digit 时它会用零补上, 超过 digit 时它会四舍五入进位.

<h1>{{ 1.10 | number : '2.2-4' }}</h1>   <!--01.10 前面补上了一个 0 -->
<h1>{{ 0.1 | number : '.2-4' }}</h1>     <!--0.10 后面补上了一个 0-->
<h1>{{ .123 | number : '1.2-4' }}</h1>   <!--0.123 前面补上了一个 0-->
<h1>{{ .12345 | number : '1.2-4' }}</h1> <!--0.1235 后面四舍五入进位-->

注: 它的 default 是 '1.0-3', 另外, 小数点前只能控制最少 digit, 不能控制最多.

 

PercentPipe

PercentPipe 的作用时把 0.57 变成 '57%'

<h1>{{ 0.57 | percent }}</h1>

效果

它也支持 digit setting

<h1>{{ 0.575 | percent : '.0' }}</h1> <!--58%-->

p.s. '.0' 相等于 '1.0-0', best practice 是写完整的 '1.0-0'

 

CurrencyPipe

CurrentPipe 会 transform number 变成带有 current code.

<h1>{{ 100 | currency : 'USD' }}</h1> <!-- $100.00 默认是显示 symbol 哦 -->
<h1>{{ 100 | currency : 'USD' : 'code' }}</h1> <!-- USD100.00 -->
<h1>{{ 100 | currency : 'USD' : 'symbol' }}</h1> <!-- $100.00 -->
<h1>{{ 100 | currency : 'MYR' : 'RM' }}</h1> <!-- RM100.00 -->

第一个参数是 currency code ISO 4217

第二个参数是 display mode. 

'code' 表示显示 currency code

'symbol' 表示显示符号就好了, Angular 会把 code 转成对应的 symbol

‘String’ 有时候我们想 display alias 比如马来西亚习惯看 'RM' Ringgit Malaysia, 这时就可以直接写 string 'RM'

第三个参数是 digit setting. 默认是 '1.2-2'

 

JsonPipe, SlicePipe, AsyncPipe

其它 build-in pipes.

<h1>{{ { name: 'Derrick' } | json }}</h1> <!-- { "name": "Derrick" } -->
<h1>{{ ['a', 'b', 'c', 'd'] | slice : 1 : -1 }}</h1> <!-- ['b', 'c'] -->
<h1>{{ value$ | async }}</h1> <!-- value1---value2---value3 -->

async pipe 常用于处理 PromiseRxJS Stream, 通常还会搭配 *ngIf as else loading, 但这里我先不展开, 之后会再提到.

 

Global Configuration

上面例子中有些需要传入 parameter. 比如 | currency : 'USD', | date : 'dd MMM yyyy'

许多时候整个项目的 currency, date format 都是一致的. 这时就需要一个 global config.

Pipe 的 global config 是利用 DI 来实现的

如果想设置全局, 可以到 app.config.ts 写入 provider 就可以了

import { DATE_PIPE_DEFAULT_OPTIONS, DatePipeConfig } from '@angular/common';
import { ApplicationConfig, DEFAULT_CURRENCY_CODE } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: DEFAULT_CURRENCY_CODE, useValue: 'USD' },
    {
      provide: DATE_PIPE_DEFAULT_OPTIONS,
      useValue: {
        dateFormat: 'dd MMM yyyy',
        timezone: '+0800', // if empty string, then Angular will use end-user's local system timezone
      } satisfies DatePipeConfig,
    },
  ],
};

DEFAULT_CURRENCY_CODE 和 DATE_PIPE_DEFAULT_OPTIONS 是 InjectionToken

 

Use Pipe Transform in Any Where

大部分 Angular pipe 都提供了底层方法, 所以可以用在任何地方

import { formatDate, formatCurrency, formatNumber, formatPercent} from '@angular/common';

console.log(formatDate(new Date(), 'dd MMM yyyy', 'en-US')); // 06 Apr 2023
console.log(formatCurrency(100, 'en-US', 'RM', 'MYR')); // RM100.00

当然, 它们不在 DI 的 cover 范围, 也就无法使用 global config 了.

如果想在组件内使用 pipe, 可以通过 DI

View Code

DatePipe by default 是没有在 DI 中的, 所以我们需要 provide

可以在 app.config.ts provide, 也可以通过 Component metadata provide (区别是什么, 我会在接下来章节详解讲解, 这里不展开)

然后注入就可以使用了, 这样它就能使用到 global config 了.

Custom Pipe

上面提及的都是 Angular build-in 的 pipe. 虽然挺多的, 但在真实项目中依然不够用.

幸好, Angular 允许我们自定义 Pipe. (其实, Angular build-in 的 pipe 也是用同一种方式添加进项目的哦)

我们尝试做一个 AgoPipe

CLI command

ng g p ago

p for pipe

一开始长这样

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

@Pipe({
  name: 'ago',
  standalone: true
})
export class AgoPipe implements PipeTransform {
  transform(value: unknown, ...args: unknown[]): unknown {
    return null;
  }
}

接着我们加入逻辑

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

// 如果有需要 global config
export interface AgoPipeConfig {}
export const AGO_PIPE_CONFIG_TOKEN = new InjectionToken<AgoPipeConfig>(
  'AgoPipeConfig'
);
// 底层方法
export function formatAgo(
  date: Date,
  param1?: string,
  config: AgoPipeConfig | null = null
) {
  console.log([date, param1, config]); // value, parameter, config
  // do any transformation (这里我省略掉具体实现方法)
  return '7 days ago';
}

@Pipe({
  name: 'ago',
  standalone: true,
})
export class AgoPipe implements PipeTransform {
  private agoPipeConfig = inject(AGO_PIPE_CONFIG_TOKEN, { optional: true }); // 注入 global config

  transform(date: Date, param1?: string): string {
    // 调用底层方法
    return formatAgo(date, param1, this.agoPipeConfig);
  }
}

看注释理解, 里面包含了 global config 和底层方法. 类似 Angular 的 formatCurrency 和 DEFAULT_CURRENCY_CODE

在 app.config.ts 提供 global config

import { ApplicationConfig } from '@angular/core';
import { AgoPipeConfig, AGO_PIPE_CONFIG_TOKEN } from './ago.pipe';

export const appConfig: ApplicationConfig = {
  providers: [
    { provide: AGO_PIPE_CONFIG_TOKEN, useValue: {} satisfies AgoPipeConfig },
  ],
};

在组件 imports AgoPipe

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, AgoPipe], // imports AgoPipe
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  constructor() {}
  today = new Date();
}

BTW, Angular build-in 的 pipe 是封装在 CommonModule 里的.

最后, 在 template 使用

<h1>{{ today | ago }}</h1>

这样就可以了.