Dynamic pipe in Angular 2

后端 未结 6 882
失恋的感觉
失恋的感觉 2020-12-01 12:35

I\'m trying to create a component where you can pass which pipe that should be used for a list inside the component. From what I could find by testing and looking around for

相关标签:
6条回答
  • 2020-12-01 13:06

    Unfortunately I don't think so. It's the same as in angular1 where you have a function return a string for the dynamic Pipe you want.

    Looking at the docs that's exactly how they show it as well.

    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>
    `
    

    Then in the controller:

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

    Alas, it could be worse! :)

    0 讨论(0)
  • 2020-12-01 13:10

    The easiest way to tackle this would be to not use the pipes in the HTML templates, but instead, inject the pipe into a component's constructor (using DI), then apply the transform functionally. This works quite well with an Observable map or similar rxjs streams.

    0 讨论(0)
  • 2020-12-01 13:17

    I handled this by sending the pipe provider to the component and it runs the transform method. And It works with Angular 9. I hope it helps someone! Demo: 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 { }
    
    0 讨论(0)
  • 2020-12-01 13:24

    Building on borislemke's answer, here's a solution which does not need eval() and which I find rather clean:

    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 = [];
    }
    
    0 讨论(0)
  • 2020-12-01 13:26

    Building on @Balu answer this what I had to do to get it working with 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 讨论(0)
  • 2020-12-01 13:27

    I managed to get something working, it's a bit dirty and evil (with eval) but it does the trick for me. In my case, I have a table component with different data types in each row (e.g title, url, date, status). In my database, status is marked as either 1 as enabled or 0 for disabled. Of course, it is more preferable to be showing enabled/disabled to my user. Also, my title column is multilingual, which makes it an object with either en or id as it's key.

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

    Ideally, I need 2 different pipes to convert my data to show to my app's user. Something like translateTitle and getStatus will do fine. Let's call the parent's pipe 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]
        }
    }
    

    I'll probably get a lot of hate on using eval. Hope it helps!

    Update: when you might need it

    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>
    

    Will return:

    | title          | url            | date           | status         |
    | Some post t...   a-beautiful...   an hour ago      enabled
    | Some post ...2   a-beautifu...2   2 days ago       disabled
    
    0 讨论(0)
提交回复
热议问题