Sampath
Sampath

Reputation: 65978

Angular generic pipe

I would like to write generic pipe. This is the pipe where I have written for data type Category

order-by-category.ts

import { Pipe, PipeTransform } from '@angular/core';
import { Category } from '../../models/Category';
import { sortBy } from 'lodash';


@Pipe({
  name: 'orderByCategory',
})
export class OrderByCategoryPipe implements PipeTransform {
  transform(value: Category[]): Category[] {
    return sortBy(value, (o: Category) => { return o.name; });
  }
}

.html

 <ion-item *ngFor="let c of categorySortedList | orderByCategory">
      <ion-label>{{c.name }}</ion-label>
      <ion-radio [value]="c.id" (ionSelect)="selectedCategory(c)"></ion-radio>
 </ion-item>

Now I need to write the same kind of pipe. But the data type is different.

Now it is like this:

 transform(value: BudgetGroup[]): BudgetGroup[] {
    return sortBy(value, (o: BudgetGroup) => { return o.name; });
  }

So I would like to use generic solution here rather than the same kind of 2 pipes and also I need to maintain Typed pipe too.

Upvotes: 8

Views: 10387

Answers (3)

Crocsx
Crocsx

Reputation: 7640

I made a similar question here : A way to cast variables in template to multiple types and since I went trough this post like 10 times when searching online, I would like to post a hacky solution here too.

I made the following simple pipe :


@Pipe({   name: 'toType',   pure: true, }) export class ToTypePipe
implements PipeTransform {   transform<T>(value: unknown, _: T): T {
    return value as T;   } }

You can call it like

value | toType: Animal.DOG

value can be anything of Animal but we just cast is as one of the value, so the compiler consider it ok to use it and do not complain.

IT IS NOT SAFE AND KIND OF REMOVE THE PURPOSE OF STRICT MODE.

But, it allow to do some workaround and make some very simple cast easy. like in this case :

[cdkColumnDef]="columns.name.def">   <td class="align-items-center p-4
w-80" cdk-cell *cdkCellDef="let element">  ```

`*cdkCellDef="let element"` this IS an `Animal` but angular template
do not allow us to have the correct typing, so in this case `let
element | typeOf: Animal.DOG` should be completely safe

Upvotes: 3

Daniel Kucal
Daniel Kucal

Reputation: 9242

I'm just writing the same and found your question. The answer is quite obvious - generic function will work:

@Pipe({
  name: 'orderByName',
})
export class OrderByNamePipe implements PipeTransform {
  transform<T extends {name: string}>(value: T[]): T[] {
    return value.sort((a, b) => {
      if (a.name > b.name) return -1;
      if (a.name < b.name) return 1;
      return 0;
    });
  }
}

The original type is kept and seen in templates 🤓

Upvotes: 24

amal
amal

Reputation: 3170

Trying to clarify what @Rob has already pointed out, with code. You can generalize your pipe's transform() method to conform to a certain type that has a mandatory 'name' like this.

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

export interface NameModelForPipe {
 name: string; // forces the 'name' property
 [key: string]: any; // allows for basically another property of 'any' type
}

@Pipe({
  name: 'orderByNameModel',
})
export class OrderByNameModelPipe implements PipeTransform {
  transform(value: NameModelForPipe[]): NameModelForPipe[] {
    return sortBy(value, (o: NameModelForPipe) => { return o.name; });
  }
}

If you don't want to declare an interface explicitly, you can specify it in-line,

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


@Pipe({
  name: 'orderByNameModel',
})
export class OrderByNameModelPipe implements PipeTransform {
  transform(value: { name: string; [key: string]: any; }[]): { name: string; [key: string]: any; }[] {
    return sortBy(value, (o: { name: string; [key: string]: any; }) => { return o.name; });
  }
}

Upvotes: 3

Related Questions