Вопрос или проблема
У меня есть несколько пользовательских пайпов, таких как этот:
@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);
}
Ответ или решение
Для решения вопроса о создании универсальной функции, которая принимает 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 или его экземпляра зависит от ваших архитектурных предпочтений и требований. Использование обобщений позволяет сохранять типизацию и избежать ошибок времени компиляции, что делает ваш код более безопасным и поддерживаемым.