Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to inject pipes in HTML dynamically? Angular 9

The subject
I'm building an abstract table component to which I pass what pipe it should use in certain columns. As the data passed may vary, so the pipes should vary as well.

The goal
To use whatever pipe is passed to the table

The project
Here's how it should look like in html in my opinion

<!-- html --> 

<span *ngIf="element.pipe">{{ row[element.column] | <<here_inject_an_appropriate_pipe>> }}</span>

The column settings are passed through an object and have form of

//typescript  

columnSettings: [
    ...
    {column: 'fruitExpDate', caption: 'Best before', pipe: 'date: \"' + PIPE_DATE_FORMAT + '\"' },
    ...
]

and PIPE_DATE_FORMAT holds the string 'yyyy-MM-dd'

What I tried

  1. Passing the pipe directly through a variable like
<!-- html --> 

<span *ngIf="element.pipe">{{ row[element.column] | element.pipe }}</span>
  1. Creating custom pipe which takes another pipe as an argument
@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';
    }
}

and here I tried many different things to call the requested pipe but eventually didn't figure out how to do this. Here's my html with the custom pipe:

<!-- html --> 

<span *ngIf="element.pipe">{{ row[element.column] | dynamicPipe: element.pipe }}</span>
  1. Creating a custom service to call imported pipes
@Injectable()
export class AbstractTableService {
    constructor(
        private date: DatePipe,
    ) {}

    getDatePipe(): DatePipe {
        return this.date;
    }
}

but here I had no idea how to use this service effectively.

like image 569
big_OS Avatar asked Jul 20 '20 06:07

big_OS


People also ask

How do you use pipe in angular?

You use data binding with a pipe to display values and respond to user actions. If the data is a primitive input value, such as String or Number, or an object reference as input, such as Date or Array, Angular executes the pipe whenever it detects a change for the input value or reference.

What are the built-in pipes for data transformation in angular?

Angular contains built-in pipes for data transformations. The following are commonly used built-in pipes for data formatting: DatePipe: Formats a date value according to locale rules. UpperCasePipe: It transforms text to all upper case. LowerCasePipe: It transforms text to all lower case. CurrencyPipe: Transforms a number to a currency string.

What is a pure pipe in angular?

A pure pipe must use a pure function, which is one that processes inputs and returns values without side effects. In other words, given the same input, a pure function should always return the same output. With a pure pipe, Angular ignores changes within composite objects, such as a newly added element of an existing array, because checking a ...

What are the prerequisites to learn angular?

Prerequisites: Basic understanding of Angular Components, Directives, and the structure of an Angular application. The pipes in Angular are of two types: The built-in pipes are those which are already given to us by the Angular and are ready to use. Whereas, in some scenarios, the built-in pipes are not sufficient to fulfill our use case.


2 Answers

You need to create an instance of the selected pipe inside a dynamic pipe. To do that, you can utilize Angular injector. The dynamic pipe (what I call it) can be something like this:

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

}

Make sure to pass the pipe class (type) as args not a string representation of its name. If you are going to pass a string, let's say the data comes from server-side, you might need to consider creating a mapping for that.

A fully working example can be found here: https://stackblitz.com/edit/angular-ivy-evzwnh

This is a rough implementation. I am not sure about Tree-Shaking. It needs more testing and optimization.

like image 109
Aboodz Avatar answered Oct 10 '22 14:10

Aboodz


pipe is NOT a string, so you can't use pipe:'date: \"' + PIPE_DATE_FORMAT + '\"'

Your second aproach is closed to you want get it, but you need use a switch case

NOTE 1: From Angular 9 you can use directly the functions: formatDate, formatNumber, formatCurrency and 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;
}

(*) check the docs to know how use the functions, I write "pseudo-code"

NOTE 2: perhafs if you create your "columns objects" with two properties, pipeKing and args- this one as an array-, your pipe becomes more confortable

e.g.

  {
   column: 'fruitExpDate', 
   caption: 'Best before', 
   pipeKind: 'date'
   pipeArgs:[PIPE_DATE_FORMAT]
  }
like image 1
Eliseo Avatar answered Oct 10 '22 15:10

Eliseo