Передача общего Pipe, реализующего PipeTransform в Angular

Вопрос или проблема

У меня есть несколько пользовательских пайпов, таких как этот:

@Pipe({ name: 'appPadThree', pure: true, standalone: true })
@Injectable()
export class PadThreePipe implements PipeTransform {
  transform(value: number): string {
    // Код
  }
}

Я пытаюсь написать универсальную функцию для использования .transform(), но .transform() не существует для типа Pipe.

Попытка 1:

  pipeData<T>(value: string, pipeName: T): string {
    const pipeWorker = inject(pipeName);
    return pipeWorker.transform(value);
  }

Попытка 2:

  pipeData<T extends PipeTransform>(value: string, pipeName: T): string {
    return pipeRef.transform(value);
  }

Ошибка: Аргумент типа ‘Pipe’ не может быть присвоен параметру типа ‘PipeTransform’. Свойство ‘transform’ отсутствует в типе ‘Pipe’, но требуется в типе ‘PipeTransform’.

Этот код работал до обновления Angular 18 / standalone. Он не использовал дженерики и был неэстетичным.

  pipeData(value: any, pipeName: any): any {
    const pipeWorker = this.injector.get(pipeName);
    return pipeWorker.transform(value);
  }

Как я могу написать универсальную функцию, которая принимает Pipe как тип, зная, что он реализует PipeTransform?

ИЗМЕНЕНИЕ
Вызов несколько усложняется. У меня есть интерфейс, который выглядит так:

export interface ITableViewer {
  ...
  displayPipe?: Pipe;
}

Мой объект выглядит так:

{ ... displayPipe: PadThreePipe },

А вызов:

this.pipeData(displayString, pipeName);

Решение для Попытки 1

Поскольку вы задаете PadThreePipe как тип для поля displayPipe:

let pipeRef = { displayPipe: PadThreePipe };
let pipeName = pipeRef.displayPipe;

this.pipeData(displayString, pipeName)

Вы должны сослаться на тип класса по функции конструктора. Ссылка: Обобщенная типизация с аргументом класса

pipeData<T>(value: string, pipeName: new () => T): string {
  const pipeWorker = inject(pipeName);
  return pipeWorker.transform(parseInt(value));
}

Решение для Попытки 2

Передайте экземпляр PadThreePipe в поле displayPipe.

let pipeRef = { displayPipe: new PadThreePipe() };
let pipeName = pipeRef.displayPipe;

this.pipeData(displayString, pipeName);

Измените название второго аргумента на pipeRef.

pipeData<T extends PipeTransform>(value: string, pipeRef: T): string {
  return pipeRef.transform(value);
}

Демо @ StackBlitz

Ответ или решение

Для решения вопроса о создании универсальной функции, которая принимает Pipe и гарантирует, что он реализует интерфейс PipeTransform, необходимо правильно использовать обобщения в TypeScript.

Шаги для решения проблемы

1. Определение универсальной функции

Сначала определим функцию так, чтобы она принимала класс Pipe как аргумент:

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

function pipeData<T extends PipeTransform>(value: string, pipeClass: new () => T): string {
    const pipeInstance = inject(pipeClass);
    return pipeInstance.transform(value);
}

Такой способ позволяет передавать класс Pipe как аргумент и создавать его экземпляр с помощью inject, что при этом гарантирует, что мы получим экземпляр, реализующий метод transform.

2. Использование функции

Теперь, когда мы создали функцию, мы можем использовать её в контексте вашего примера с PadThreePipe:

@Pipe({ name: 'appPadThree', pure: true, standalone: true })
@Injectable()
export class PadThreePipe implements PipeTransform {
    transform(value: number): string {
        // Реализация метода
        return String(value).padStart(3, '0');
    }
}

// Пример использования
let pipeInstance: PadThreePipe;
const transformedValue = pipeData('5', PadThreePipe);
console.log(transformedValue); // Например, "005"

3. Другой способ: Передача экземпляра Pipe

Если вы хотите передать уже созданный экземпляр Pipe, вы можете изменить функцию следующим образом:

function pipeData<T extends PipeTransform>(value: string, pipeInstance: T): string {
    return pipeInstance.transform(value);
}

И использование будет выглядеть так:

let pipeRef = new PadThreePipe();
const transformedValue = pipeData('5', pipeRef);
console.log(transformedValue); // Например, "005"

4. Применение в компоненте

Если вам нужно работать с интерфейсами и гарантировать, что ваш объект содержит Pipe, который будет использоваться:

export interface ITableViewer {
    displayPipe?: new () => PipeTransform; // Принимаем класс, который реализует PipeTransform
}

// Пример использования
const tableViewer: ITableViewer = {
    displayPipe: PadThreePipe
};

if (tableViewer.displayPipe) {
    const transformedValue = pipeData('5', tableViewer.displayPipe);
    console.log(transformedValue); // Например, "005"
}

Заключение

Эти примеры демонстрируют универсальный подход к созданию функции, которая принимает Pipe и использует его метод transform. Выбор между передачей класса Pipe или его экземпляра зависит от ваших архитектурных предпочтений и требований. Использование обобщений позволяет сохранять типизацию и избежать ошибок времени компиляции, что делает ваш код более безопасным и поддерживаемым.

Оцените материал
Добавить комментарий

Капча загружается...