Как написать кастомный декоратор для Angular?angular-80

Что такое декоратор в Angular?

Декоратор — это специальная функция, которая модифицирует классы, методы, свойства или параметры, добавляя к ним метаданные или изменяя их поведение. В Angular встроенные декораторы (@Component, @Injectable, @Input и др.) используются для определения метаданных классов.


Зачем создавать кастомный декоратор?

  1. Добавление общей логики (например, логирование, проверка прав).
  2. Упрощение повторяющегося кода (декораторы DRY-friendly).
  3. Модификация поведения (например, автоматическая отписка от Observable).

Создание простого декоратора метода

Пример: Декоратор для логирования вызовов метода

export function Log() {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      console.log(`Вызван метод ${propertyKey} с аргументами:`, args);
      const result = originalMethod.apply(this, args);
      console.log(`Метод ${propertyKey} вернул:`, result);
      return result;
    };

    return descriptor;
  };
}

Использование:

@Component({...})
export class MyComponent {
  @Log()
  calculateSum(a: number, b: number): number {
    return a + b;
  }
}

Декоратор класса для Angular

Пример: Декоратор, добавляющий обработчик ngOnDestroy

export function AutoUnsubscribe() {
  return function(constructor: any) {
    const originalNgOnDestroy = constructor.prototype.ngOnDestroy;

    constructor.prototype.ngOnDestroy = function() {
      for (const prop in this) {
        const property = this[prop];
        if (property && typeof property.unsubscribe === 'function') {
          property.unsubscribe();
        }
      }
      originalNgOnDestroy?.apply(this);
    };
  };
}

Использование:

@AutoUnsubscribe()
@Component({...})
export class MyComponent implements OnDestroy {
  data$ = someObservable.pipe(...);

  ngOnDestroy() {
    console.log('Компонент уничтожается');
  }
}

Декоратор свойства с динамическим изменением поведения

Пример: Декоратор для кэширования геттера

export function Cache() {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const getter = descriptor.get;
    if (!getter) throw new Error('@Cache можно применять только к геттерам');

    const cacheKey = `__cached_${propertyKey}`;

    descriptor.get = function() {
      if (!this[cacheKey]) {
        this[cacheKey] = getter.apply(this);
      }
      return this[cacheKey];
    };
  };
}

Использование:

@Component({...})
export class MyComponent {
  private _heavyData: any;

  @Cache()
  get processedData() {
    return this._heavyData.map(...); // Тяжелая операция
  }
}

Декораторы с параметрами

Пример: Декоратор для проверки прав доступа

export function CheckPermission(permission: string) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      if (!this.authService.hasPermission(permission)) {
        throw new Error('Доступ запрещен!');
      }
      return originalMethod.apply(this, args);
    };
  };
}

Использование:

@Component({...})
export class AdminPanelComponent {
  @CheckPermission('admin')
  deleteUser(userId: string) {
    // Логика удаления
  }
}

Особенности работы с Angular

  1. Декораторы компилируются в runtime, поэтому:

    • Нельзя использовать для AOT-компиляции без дополнительных шагов.
    • Для production-сборок нужны дополнительные плагины (например, babel-plugin-annotations).
  2. Влияние на производительность:

    • Сложные декораторы могут замедлить приложение.
    • Рекомендуется использовать их для dev-режима или админ-функций.

Резюмируем

  • Кастомные декораторы в Angular создаются как функции, возвращающие функции-обертки.
  • Можно декорировать классы, методы, свойства и параметры.
  • Полезны для добавления сквозной функциональности.
  • Требуют осторожного использования в production-коде.