Angular 2中的动态管道

23

我正在尝试创建一个组件,其中可以传递应该用于组件内部列表的管道。根据我的测试和寻找答案,唯一的解决方案似乎是创建类似以下内容的东西:

<my-component myFilter="sortByProperty"></my-component>

my-component模板:

<li *ngFor="#item of list | getPipe:myFilter"></li>

然后将myFilter映射到正确的管道逻辑并运行它,但这似乎有点不太干净且不够优化。

我认为自从Angular 1以来,他们应该已经想出了更好的解决方案,您也会做类似的事情。

在Angular 2中没有更好的方法吗?


你的 getpipe 是自定义过滤器吗? - tsadkan yitbarek
不仅可以在AngularJS中实现这一点,而且在Angular +2中也可以。该技术类似于@Balu在下面发布的内容。 - SoEzPz
8个回答

58

在borislemke的答案基础上,这里提供一个不需要使用eval()且比较简洁的解决方案:

dynamic.pipe.ts:

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


@Pipe({
  name: 'dynamicPipe'
})
export class DynamicPipe implements PipeTransform {

    public constructor(private injector: Injector) {
    }

    transform(value: any, pipeToken: any, pipeArgs: any[]): any {
        if (!pipeToken) {
            return value;
        }
        else {
            let pipe = this.injector.get(pipeToken);
            return pipe.transform(value, ...pipeArgs);
        }
    }
}

app.module.ts:

// …
import { DynamicPipe } from './dynamic.pipe';

@NgModule({
  declarations: [
    // …
    DynamicPipe,
  ],
  imports: [
    // …
  ],
  providers: [
    // list all pipes you would like to use
    PercentPipe,
    ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts:

import { Component, OnInit } from '@angular/core';
import { PercentPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  template: `
    The following should be a percentage: 
    {{ myPercentage | dynamicPipe: myPipe:myPipeArgs }}
    `,
  providers: []
})

export class AppComponent implements OnInit {
  myPercentage = 0.5;
  myPipe = PercentPipe;
  myPipeArgs = [];
}

4
我无法让这个工作。我遇到了以下错误:Error: StaticInjectorError(AppModule)[date]: StaticInjectorError(Platform: core)[date]: NullInjectorError: No provider for date! - Chuck
@Chuck 你是否已经将 DatePipe 添加到你的应用程序模块的提供商中了? - balu
所有的管道都通过一个共享模块导出,该模块在app.module中提供。当我按照上述方式实现代码时,它可以正常工作。但是,我在我的代码中将myPipe设置为@Input() pipe,以便可以通用使用。 - Chuck
1
@Chuck如果你找到了答案,请在评论区或回答中说明你是如何找到答案的。其他人也想知道。 - camainc
@Shawn,恐怕我已经离开Angular业务太久了,所以需要其他人来发表意见。 - balu
显示剩余2条评论

4

处理这个问题最简单的方法是不在HTML模板中使用管道,而是将管道注入到组件的构造函数中(使用DI),然后将转换函数应用于它。这在Observable映射或类似的rxjs流中非常有效。


很好的建议,但如果要按照我想要的方式使用,仍然需要一个包装服务。 - Chrillewoodz

3
很不幸,我认为不行。这与Angular1中的情况相同,在那里你需要一个函数返回一个字符串来获取你想要的动态管道。
从文档上看,这正是他们展示的方式。

https://angular.io/docs/ts/latest/guide/pipes.html

template: `
   <p>The hero's birthday is {{ birthday | date:format }}</p>
   <button (click)="toggleFormat()">Toggle Format</button>
`

然后在控制器中:

get format()   { return this.toggle ? 'shortDate' : 'fullDate'}

唉,情况可能更糟!:)


3

我设法让某些东西起作用,虽然有点肮脏和邪恶(使用 eval),但对我来说这很管用。在我的情况下,我有一个包含不同数据类型的表格组件,每行都有不同的数据类型(例如标题、网址、日期、状态)。在我的数据库中,状态标记为 1 表示 启用0 表示 禁用。当然,向用户显示已启用或已禁用更为可取。此外,我的标题列是多语言的,因此它是一个带有 enid 作为键的对象。

// Example row object:
title: {
    "en": "Some title in English",
    "id": "Some title in Indonesian"
},
status: 1 // either 1 or 0

理想情况下,我需要2个不同的管道将我的数据转换为适合于应用程序用户查看的格式。例如像translateTitlegetStatus这样的内容就可以了。我们称父级管道为dynamicPipe
/// some-view.html
{{ title | dynamicPipe:'translateTitle' }}
{{ status | dynamicPipe:'getStatus' }}


/// dynamic.pipe.ts
//...import Pipe and PipeTransform

@Pipe({name:'dynamicPipe'})
export class DynamicPipe implements PipeTransform {

    transform(value:string, modifier:string) {
        if (!modifier) return value;
        return eval('this.' + modifier + '(' + value + ')')
    }

    getStatus(value:string|number):string {
        return value ? 'enabled' : 'disabled'
    }

    translateTitle(value:TitleObject):string {
        // defaultSystemLanguage is set to English by default
        return value[defaultSystemLanguage]
    }
}

我可能会因为使用eval而受到很多批评。希望它能有所帮助!

更新:何时可能需要它

posts = {
    content: [
        {
            title:
                {
                    en: "Some post title in English",
                    es: "Some post title in Spanish"
                },
            url: "a-beautiful-post",
            created_at: "2016-05-15 12:21:38",
            status: 1
        },
        {
            title:
                {
                    en: "Some post title in English 2",
                    es: "Some post title in Spanish 2"
                },
            url: "a-beautiful-post-2",
            created_at: "2016-05-13 17:53:08",
            status: 0
        }
    ],
    pipes: ['translateTitle', null, 'humanizeDate', 'getStatus']
}

<table>
    <tr *ngFor="let row in posts">
        <td *ngFor="let column in row; let i = index">{{ column | dynamicPipe:pipes[i] }}</td>
    </tr>
</table>

将返回:

| title          | url            | date           | status         |
| Some post t...   a-beautiful...   an hour ago      enabled
| Some post ...2   a-beautifu...2   2 days ago       disabled

2

在@Balu的回答基础上,这是我在Angular 9中需要做的才能使其正常工作:

import { Injector, Pipe, PipeTransform } from '@angular/core';
import { PercentPipe, CurrencyPipe, DecimalPipe } from '@angular/common';

@Pipe({
    name: 'dynamicPipe'
})

export class DynamicPipe implements PipeTransform {

    public constructor(private injector: Injector, private percentPipe: PercentPipe) {
    }

    transform(value: any, pipeToken: any, pipeArgs: any[]): any {

        const MAP = { 'currency': CurrencyPipe, 'decimal': DecimalPipe, 'percent': PercentPipe }

        if (pipeToken && MAP.hasOwnProperty(pipeToken)) {
            var pipeClass = MAP[pipeToken];
            var pipe = this.injector.get(pipeClass);
            if (Array.isArray(pipeArgs)) {
                return pipe.transform(value, ...pipeArgs);
            } else {
                return pipe.transform(value, pipeArgs);
            }
        }
        else {
            return value;
        }
    }
}

0
如果您想动态地传递多个管道,可以使用此实现方式。
(@shawn的扩展解决方案)
import { Injector, Pipe, PipeTransform } from '@angular/core';
import { DecimalPipe, SlicePipe } from '@angular/common';
import { TitlecasePipe } from './titlecase.pipe';

@Pipe({
  name: 'dynamicPipe',
})
export class DynamicPipe implements PipeTransform {
  public constructor(private injector: Injector) {}

  transform(value: any, pipeToken: any, pipeArgs: any[]): any {
    console.log(value, pipeToken);

    const MAP = {
      titlecase: TitlecasePipe,
      decimal: DecimalPipe,
      slice: SlicePipe,
    };

    const func = (pipe: any, args: any, val) => {
      if (pipe && MAP.hasOwnProperty(pipe)) {
        var pipeClass = MAP[pipe];
        var pipe = this.injector.get<any>(pipeClass);
        return Array.isArray(args)
          ? pipe.transform(val, ...args)
          : pipe.transform(val, args);
      } else {
        return val;
      }
    };

    if (!Array.isArray(pipeToken)) {
      return func(pipeToken, pipeArgs, value);
    } else {
      return pipeToken.reduce((acc, curr, index) => {
        return func(curr, pipeArgs[index], acc);
      }, value);
    }
  }
}

在 HTML 中,将 pipeToken 作为数组传递,并使用相同的索引维护 pipeArgs 数组。
 {{ title | dynamicPipe: ["slice" , "titlecase"] :[6]}}

0

我已经给@balu的回答添加了一些类型。

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 || []));
  }
}

0
我通过将管道提供者发送到组件并运行转换方法来处理此问题。它可以在Angular 9中使用。希望能对某些人有所帮助!演示:https://stackblitz.com/edit/angular-kdqc5e

pipe-injector.component.ts:

import { Component, OnInit, Input, PipeTransform } from '@angular/core';
    @Component({
      selector: 'pipe-injector',
      template: `
        Should inject my pipe provider 
        {{ getText() }}
        `,
      providers: []
    })


    export class PipeInjectorComponent {
      @Input() pipeProvider: PipeTransform;
      @Input() pipeArgs: Array<any>;
      @Input() textToFormat: string;

      getText() {
        return this.pipeProvider.transform(this.textToFormat, ...this.pipeArgs);
      }
    }

app-component.ts:

import { Component, OnInit } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-root',
  template: `
    <pipe-injector [pipeProvider]="pipeProvider" [pipeArgs]="pipeArgs" textToFormat='05-15-2020'> 
    </pipe-injector>
    `,
  providers: []
})

export class AppComponent implements OnInit {
  pipeArgs = ['dd/MM/yyyy'];
  constructor(public pipeProvider: DatePipe) {}
}

app.module.ts:

import { DatePipe } from '@angular/common';
import { PipeInjectorComponent } from './pipe-injector.component';

@NgModule({
  declarations: [

    PipeInjectorComponent,
  ],
  imports: [
  ],
  providers: [
    DatePipe,
    ],
  bootstrap: [AppComponent]
})
export class AppModule { }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接