有没有办法动态地在 HTML 中注入管道? Angular 9
Is there a way to inject pipes in HTML dynamically? Angular 9
主题
我正在构建一个抽象的 table 组件,我将它传递给它应该在某些列中使用的管道。由于传递的数据可能会有所不同,因此管道也应该有所不同。
目标
使用传递给 table
的任何管道
项目
在我看来 html
应该是这样的
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | <<here_inject_an_appropriate_pipe>> }}</span>
列设置通过对象传递,形式为
//typescript
columnSettings: [
...
{column: 'fruitExpDate', caption: 'Best before', pipe: 'date: \"' + PIPE_DATE_FORMAT + '\"' },
...
]
和PIPE_DATE_FORMAT
保存字符串'yyyy-MM-dd'
我试过的
- 直接通过变量传递管道,如
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | element.pipe }}</span>
- 正在创建将另一个管道作为参数的自定义管道
@Pipe({
name: 'dynamicPipe',
})
export class DynamicPipe implements PipeTransform {
// constructor(private abstractTableService: AbstractTableService) {}
transform(value: any, pipe: string): any {
const pipeToken: any = pipe.split(':')[0].replace(/[\s]+/g, '');
const pipeArgs: any = pipe.split(':')[1].replace(/[\s]+/g, '');
console.log(value);
console.log(pipe);
// return pipeToken.transform(value, ...pipeArgs);
return 'check pipe';
}
}
在这里我尝试了很多不同的方法来调用请求的管道,但最终没有弄清楚如何做到这一点。这是我的 html 和自定义管道:
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | dynamicPipe: element.pipe }}</span>
- 创建自定义服务以调用导入的管道
@Injectable()
export class AbstractTableService {
constructor(
private date: DatePipe,
) {}
getDatePipe(): DatePipe {
return this.date;
}
}
但在这里我不知道如何有效地使用这项服务。
pipe 不是字符串,所以你不能使用 pipe:'date: \"' + PIPE_DATE_FORMAT + '\"'
你的第二个方法已关闭,你想得到它,但你需要使用 switch case
注意 1:从 Angular9 开始,您可以直接使用函数:formatDate、formatNumber、formatCurrency 和 formatPercent
import { formatDate,formatNumber,formatCurrency,formatPercent } from '@angular/common';
transform(value: any, pipe: string): any {
const pipeToken: any = pipe.split(':')[0].replace(/[\s]+/g, '');
const pipeArgs: any = pipe.split(':')[1].replace(/[\s]+/g, '');
let result=value;
switch (pipeToken)
{
case "date":
result=formatDate(value,pipeArgs) //(*)
break
case "number"
result=formatNumber(value,pipeArgs) //(*)
break
...
}
return result;
}
(*) 查看文档了解如何使用函数,我写了“伪代码”
注意 2:也许如果您创建具有两个属性的“列对象”,pipeKing 和 args——这个作为数组——,您的管道会变得更舒适
例如
{
column: 'fruitExpDate',
caption: 'Best before',
pipeKind: 'date'
pipeArgs:[PIPE_DATE_FORMAT]
}
尝试更改您的原始模型并将管道类型和管道参数分开。这会很有帮助,比方说,你有这个:
columnSettings: [
...
{
column: 'fruitExpDate',
caption: 'Best before',
pipeType: 'date',
pipeParams: PIPE_DATE_FORMAT
},
...
]
然后您可以在 html 中编写 'dynamic pipe' 逻辑,并直接使用现有管道:
<ng-container [ngSwitch]="element.pipeType">
<ng-container *ngSwitchCase="'date'">{{ row[element.column] | date:element.pipeParams }}</ng-container>
<ng-container *ngSwitchCase="'...'">...</ng-container>
<some-element *ngSwitchDefault>...</some-element>
</ng-container>
对于条件管道,您可以映射这些项目并进行相应的转换。
columnSettings: [
...
{
column: 'fruitExpDate',
caption: 'Best before',
pipeType: 'date',
pipeParams: PIPE_DATE_FORMAT
},
...
]
columnSettings = columnSettings.map((col) => {
if(col.pipeType === 'date) {
col.pipeVal = (new DatePipe).transform(col) // or whatever you want to pass
} else if(col.pipeType === 'blah) {
col.pipeVal = (new BlahPipe).transform(col) // or whatever you want to pass
}
return col;
})
现在在模板中您不需要任何条件语句,而只需打印 col.pipeVal
您需要在动态管道中创建所选管道的实例。为此,您可以使用 Angular 注入器。动态管道(我称之为)可以是这样的:
import { Pipe, PipeTransform, Injector, Type } from '@angular/core';
@Pipe({
name: 'dynamicPipe'
})
export class DynamicPipe implements PipeTransform {
constructor(private injector: Injector) {}
transform(value: any, requiredPipe: Type<any>, pipeArgs: any): any {
const injector = Injector.create({
name: 'DynamicPipe',
parent: this.injector,
providers: [
{ provide: requiredPipe }
]
})
const pipe = injector.get(requiredPipe)
return pipe.transform(value, pipeArgs);
}
}
确保将管道 class(类型)作为参数而不是其名称的字符串表示形式传递。如果你要传递一个字符串,假设数据来自服务器端,你可能需要考虑为此创建一个映射。
可以在此处找到完整的示例:
https://stackblitz.com/edit/angular-ivy-evzwnh
这是一个粗略的实现。我不确定 Tree-Shaking。它需要更多的测试和优化。
import { Pipe, PipeTransform } from '@angular/core';
export type OmitFirstArg<T extends unknown[]> = T extends [unknown, ...infer U] ? U : never;
@Pipe({
name: 'dynamicPipe',
pure: true
})
export class DynamicPipe<P extends PipeTransform> implements PipeTransform {
public transform(
value: Parameters<P['transform']>[1],
pipeTransform: P,
pipeArgs?: OmitFirstArg<Parameters<P['transform']>>): ReturnType<P['transform']> | unknown {
if (!('transform' in pipeTransform)) {
return value;
}
return pipeTransform.transform(value, ...(pipeArgs || []));
}
}
主题
我正在构建一个抽象的 table 组件,我将它传递给它应该在某些列中使用的管道。由于传递的数据可能会有所不同,因此管道也应该有所不同。
目标
使用传递给 table
项目
在我看来 html
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | <<here_inject_an_appropriate_pipe>> }}</span>
列设置通过对象传递,形式为
//typescript
columnSettings: [
...
{column: 'fruitExpDate', caption: 'Best before', pipe: 'date: \"' + PIPE_DATE_FORMAT + '\"' },
...
]
和PIPE_DATE_FORMAT
保存字符串'yyyy-MM-dd'
我试过的
- 直接通过变量传递管道,如
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | element.pipe }}</span>
- 正在创建将另一个管道作为参数的自定义管道
@Pipe({
name: 'dynamicPipe',
})
export class DynamicPipe implements PipeTransform {
// constructor(private abstractTableService: AbstractTableService) {}
transform(value: any, pipe: string): any {
const pipeToken: any = pipe.split(':')[0].replace(/[\s]+/g, '');
const pipeArgs: any = pipe.split(':')[1].replace(/[\s]+/g, '');
console.log(value);
console.log(pipe);
// return pipeToken.transform(value, ...pipeArgs);
return 'check pipe';
}
}
在这里我尝试了很多不同的方法来调用请求的管道,但最终没有弄清楚如何做到这一点。这是我的 html 和自定义管道:
<!-- html -->
<span *ngIf="element.pipe">{{ row[element.column] | dynamicPipe: element.pipe }}</span>
- 创建自定义服务以调用导入的管道
@Injectable()
export class AbstractTableService {
constructor(
private date: DatePipe,
) {}
getDatePipe(): DatePipe {
return this.date;
}
}
但在这里我不知道如何有效地使用这项服务。
pipe 不是字符串,所以你不能使用 pipe:'date: \"' + PIPE_DATE_FORMAT + '\"'
你的第二个方法已关闭,你想得到它,但你需要使用 switch case
注意 1:从 Angular9 开始,您可以直接使用函数:formatDate、formatNumber、formatCurrency 和 formatPercent
import { formatDate,formatNumber,formatCurrency,formatPercent } from '@angular/common';
transform(value: any, pipe: string): any {
const pipeToken: any = pipe.split(':')[0].replace(/[\s]+/g, '');
const pipeArgs: any = pipe.split(':')[1].replace(/[\s]+/g, '');
let result=value;
switch (pipeToken)
{
case "date":
result=formatDate(value,pipeArgs) //(*)
break
case "number"
result=formatNumber(value,pipeArgs) //(*)
break
...
}
return result;
}
(*) 查看文档了解如何使用函数,我写了“伪代码”
注意 2:也许如果您创建具有两个属性的“列对象”,pipeKing 和 args——这个作为数组——,您的管道会变得更舒适
例如
{
column: 'fruitExpDate',
caption: 'Best before',
pipeKind: 'date'
pipeArgs:[PIPE_DATE_FORMAT]
}
尝试更改您的原始模型并将管道类型和管道参数分开。这会很有帮助,比方说,你有这个:
columnSettings: [
...
{
column: 'fruitExpDate',
caption: 'Best before',
pipeType: 'date',
pipeParams: PIPE_DATE_FORMAT
},
...
]
然后您可以在 html 中编写 'dynamic pipe' 逻辑,并直接使用现有管道:
<ng-container [ngSwitch]="element.pipeType">
<ng-container *ngSwitchCase="'date'">{{ row[element.column] | date:element.pipeParams }}</ng-container>
<ng-container *ngSwitchCase="'...'">...</ng-container>
<some-element *ngSwitchDefault>...</some-element>
</ng-container>
对于条件管道,您可以映射这些项目并进行相应的转换。
columnSettings: [
...
{
column: 'fruitExpDate',
caption: 'Best before',
pipeType: 'date',
pipeParams: PIPE_DATE_FORMAT
},
...
]
columnSettings = columnSettings.map((col) => {
if(col.pipeType === 'date) {
col.pipeVal = (new DatePipe).transform(col) // or whatever you want to pass
} else if(col.pipeType === 'blah) {
col.pipeVal = (new BlahPipe).transform(col) // or whatever you want to pass
}
return col;
})
现在在模板中您不需要任何条件语句,而只需打印 col.pipeVal
您需要在动态管道中创建所选管道的实例。为此,您可以使用 Angular 注入器。动态管道(我称之为)可以是这样的:
import { Pipe, PipeTransform, Injector, Type } from '@angular/core';
@Pipe({
name: 'dynamicPipe'
})
export class DynamicPipe implements PipeTransform {
constructor(private injector: Injector) {}
transform(value: any, requiredPipe: Type<any>, pipeArgs: any): any {
const injector = Injector.create({
name: 'DynamicPipe',
parent: this.injector,
providers: [
{ provide: requiredPipe }
]
})
const pipe = injector.get(requiredPipe)
return pipe.transform(value, pipeArgs);
}
}
确保将管道 class(类型)作为参数而不是其名称的字符串表示形式传递。如果你要传递一个字符串,假设数据来自服务器端,你可能需要考虑为此创建一个映射。
可以在此处找到完整的示例: https://stackblitz.com/edit/angular-ivy-evzwnh
这是一个粗略的实现。我不确定 Tree-Shaking。它需要更多的测试和优化。
import { Pipe, PipeTransform } from '@angular/core';
export type OmitFirstArg<T extends unknown[]> = T extends [unknown, ...infer U] ? U : never;
@Pipe({
name: 'dynamicPipe',
pure: true
})
export class DynamicPipe<P extends PipeTransform> implements PipeTransform {
public transform(
value: Parameters<P['transform']>[1],
pipeTransform: P,
pipeArgs?: OmitFirstArg<Parameters<P['transform']>>): ReturnType<P['transform']> | unknown {
if (!('transform' in pipeTransform)) {
return value;
}
return pipeTransform.transform(value, ...(pipeArgs || []));
}
}