Experts in Angular

Três Pilares do AngularDecorators no TypeScript para Projetos Angular

Decorators no TypeScript para Projetos Angular

Decoradores no TypeScript são uma funcionalidade poderosa e elegante, mas entender exatamente o que são pode ser um pouco confuso, especialmente quando começamos a misturar termos. Então, vamos desmistificar isso de uma vez por todas.

Decorators, Anotações, Modificadores: Desvendando a Torre de Babel do TypeScript

Se você já se aventurou pelo mundo do TypeScript, provavelmente já se deparou com uma verdadeira Torre de Babel de termos: Decorators, Anotações, Modificadores… Calma, não precisa entrar em pânico! Afinal, você está aqui no expertsinangular.com para se tornar um especialista, e nós vamos te ajudar a desvendar esse mistério.

Decorators: Os Mestres da Personalização

Imagine os decorators como verdadeiros artistas do código, capazes de adicionar um toque especial às suas classes, métodos e propriedades. Com um simples @, eles transformam o seu TypeScript em uma obra-prima da programação.

Mas por que tantos nomes diferentes? Bem, a verdade é que “Decorator” é o termo oficial do TypeScript, enquanto “Anotação” é uma herança do Java. Já “Modificador” é um termo mais genérico, que pode se referir a qualquer elemento que altera o comportamento de outro.

Qual o nome certo?

No mundo do TypeScript, o termo mais usado é “Decorator”. Mas não se preocupe se você encontrar outros nomes por aí. O importante é entender o conceito por trás deles: personalizar o seu código de forma elegante e eficiente.

E por que usar Decorators?

Ah, essa é fácil! Os decorators permitem que você adicione funcionalidades extras ao seu código sem precisar modificá-lo diretamente. É como adicionar um turbo ao seu carro sem precisar mexer no motor.

Decorators: Funções que Vestem Seus Objetos com Novos Superpoderes

Se você já mergulhou no universo da programação funcional, vai se sentir em casa com os decorators. Eles são como funções que vestem seus objetos com novos superpoderes, adicionando funcionalidades extras sem precisar mexer no código original.

Uma Sinfonia de Funções

Imagine os decorators como uma orquestra de funções, cada uma com um instrumento diferente, prontas para tocar uma melodia de personalização. Eles podem ser aplicados a classes, propriedades, métodos, acessores e até mesmo parâmetros, como uma camada extra de código que aprimora o comportamento do seu objeto.

A Magia do @

A sintaxe de um decorator é simples como um passe de mágica: basta adicionar o símbolo @ antes do decorador que você deseja usar, e voilà! A mágica acontece.

function MensagemBoasVindas() {
  console.log('---Olá, eu sou um decorador simples---');
}

@MensagemBoasVindas
class NaveEstelar {
  constructor() {
    console.log('Nave estelar construída.');
  }
}

Neste exemplo, o decorator MensagemBoasVindas é aplicado à classe NaveEstelar, adicionando uma mensagem especial ao console.

// Instanciando a classe NaveEstelar
const enterprise = new NaveEstelar();

Quando você cria uma instância de NaveEstelar, verá a seguinte saída no console:

---Olá, eu sou um decorador simples---
Nave estelar construída.

Os Cinco Elementos da Decoração

Assim como os cinco elementos da natureza, os decorators também se dividem em cinco categorias:

  1. Class Decorators: Decoram a classe inteira, como um manto que envolve todo o objeto.
  2. Property Decorators: Decoram as propriedades da classe, como joias que adornam cada atributo.
  3. Method Decorators: Decoram os métodos da classe, como um bastão mágico que concede novos poderes.
  4. Accessor Decorators: Decoram os acessores (getters e setters) da classe, como um guardião que protege o acesso aos dados.
  5. Parameter Decorators: Decoram os parâmetros dos métodos, como temperos que adicionam sabor à receita.
Um Exemplo Completo
@naveDecorator
class Starship {
    @propulsorDecorator
    nome: string;

    @acessoCargaDecorator
    get carga() {}

    @metodoViagemDecorator
    viajarPara(@parametroDistanciaDecorator anosLuz: number) {}
}

Neste exemplo, vemos todos os cinco tipos de decorators em ação, decorando uma classe Starship e seus membros.

Ordem de Decolagem: Cronometrando a Jornada dos Decorators

No universo dos decorators, a ordem dos eventos é crucial, assim como a cronometragem precisa de uma viagem espacial.

O Momento da Decolagem

Os decorators são executados apenas uma vez, no momento em que a classe é definida, como uma nave que decola em direção ao desconhecido.

function aplicarDecorador(Classe) {
  console.log('Aplicar decorador');
  return Classe;
}

@aplicarDecorador
class NaveIntergalatica {}

// Saída: Aplicar decorador

Mesmo sem instanciar a classe NaveIntergalatica, o decorator aplicarDecorador já entrou em ação, mostrando sua mensagem de “apply decorator”.

“Os decorators são executados apenas uma vez, no momento em que a classe é definida” significa exatamente isso: o decorator aplicarDecorador é executado no momento em que a classe <code>NaveIntergalatica é definida, independentemente de você criar ou não uma instância (objeto) da classe.

Essa característica dos decorators é importante porque permite que eles modifiquem a classe antes mesmo de qualquer objeto ser criado. No exemplo que vimos, o decorator aplicarDecorador simplesmente imprime uma mensagem no console, mas em cenários mais complexos, ele poderia adicionar propriedades, métodos ou outros comportamentos à classe.

Essa execução antecipada dos decorators é uma das razões pelas quais eles são tão poderosos e flexíveis. Eles permitem que você personalize suas classes de maneira elegante e eficiente, sem precisar modificar o código original.

A Sequência da Jornada

Assim como em uma viagem espacial, cada etapa tem sua ordem. Os decorators também seguem uma sequência específica:

  1. Parâmetros: Os decorators de parâmetros são os primeiros a embarcar na jornada, como os passageiros que entram na nave.
  2. Métodos, Acessores e Propriedades: Em seguida, vêm os decorators de métodos, acessores e propriedades, como os tripulantes que preparam a nave para a decolagem.
  3. Construtor: O decorator do construtor é o próximo, como o piloto que assume o controle da nave.
  4. Classe: Por fim, o decorator da classe entra em cena, como o comandante que dá a ordem final para a decolagem.

Atenção aos Detalhes

É importante notar que a ordem de avaliação dos decorators de propriedades, acessores e métodos depende da ordem em que aparecem no código. No entanto, a ordem de avaliação dos decorators de parâmetros dentro do mesmo método ou construtor é inversa, como se os passageiros entrassem na nave em ordem inversa.

Decorators em Camadas: Navegando pela Estrutura da Personalização

Assim como podemos personalizar uma nave espacial com várias camadas de tecnologia, também podemos aplicar múltiplos decorators a uma mesma classe, método ou propriedade. A ordem de avaliação desses decorators segue uma estrutura bem definida, como camadas que se sobrepõem.

A Ordem da Composição

Imagine que você está decorando uma nave espacial. Primeiro, você aplica uma camada externa de pintura (o decorator externo), depois uma camada interna de isolamento (o decorator interno). A ordem de avaliação dos decorators segue a mesma lógica:

  1. Avaliação Externa: O decorator mais externo é avaliado primeiro.
  2. Avaliação Interna: Em seguida, o decorator mais interno é avaliado.
  3. Execução Interna: O decorator mais interno é executado (chamado).
  4. Execução Externa: Por fim, o decorator mais externo é executado.
Exemplo Ilustrativo
function registrarEvento(evento: string) {
  console.log("Registrando evento:", evento); // Avaliando o decorator
  return function () {
    console.log("Evento ocorrido:", evento); // Executando o decorator
  };
}

class NaveEspacial {
  @registrarEvento("decolagem") // Decorator externo
  @registrarEvento("aceleracao") // Decorator interno
  decolar() {}
}

Neste exemplo, aplicamos dois decorators (registrarEvento) ao método decolar da classe NaveEspacial. A saída no console demonstrará a ordem de avaliação e execução dos decorators:

Registrando evento: decolagem 
Registrando evento: aceleracao 
Evento ocorrido: aceleracao 
Evento ocorrido: decolagem

Perceba que o evento “aceleracao” (decorator interno) é registrado e ocorre antes do evento “decolagem” (decorator externo).

Explorando as Possibilidades

A composição de decorators permite criar personalizações em camadas, adicionando funcionalidades e comportamentos complexos aos seus objetos. Você pode combinar decorators para logging, validação, autorização, cache e muito mais.

Decorators de Classe: As Naves Capitânia da Sua Frota Estelar

Assim como as naves capitânia lideram a Frota Estelar, os decorators de classe assumem o comando da personalização das suas classes em TypeScript.

Definição e Superpoderes

Um decorator de classe é como um engenheiro da Frota Estelar que examina os planos de uma nave e decide como aprimorá-la. Ele recebe o construtor da classe como parâmetro e pode retornar uma nova classe modificada, adicionando novas propriedades, métodos ou comportamentos.

type Constructor = { new (...args: any[]): any };

function toString<T extends Constructor>(BaseClass: T) {
  return class extends BaseClass {
    toString() {
      return JSON.stringify(this);
    }
  };
}

Neste exemplo, o decorator toString adiciona um método toString a qualquer classe que o utilize, transformando seus objetos em strings JSON.

Explorando as Fronteiras da Personalização

Imagine que você quer equipar todas as suas naves estelares com um sistema de comunicação universal. Com um decorator de classe, você pode fazer isso facilmente, adicionando um método comunicar a todas as classes de naves.

function adicionarComunicacao<T extends Constructor>(BaseClass: T) {
  return class extends BaseClass {
    comunicar(mensagem: string) {
      console.log(`Transmitindo mensagem: ${mensagem}`);
    }
  };
}

Desafios da Tipagem Segura

Infelizmente, os decorators do TypeScript ainda não oferecem suporte completo para tipagem segura. Isso significa que, ao adicionar novas propriedades ou métodos a uma classe com um decorator, o compilador pode não reconhecê-los imediatamente.

// ... (código do exemplo anterior)

@adicionarComunicacao
class NaveExploradora {}

const exploradora = new NaveExploradora();
exploradora.comunicar("Contato com nova forma de vida!"); 
// O compilador pode reclamar que o método 'comunicar' não existe em 'NaveExploradora'
Solução Temporária

Uma solução comum para esse problema é criar uma classe base com as propriedades e métodos que você deseja adicionar, e então estendê-la nas classes que usarão o decorator.

class NaveBase {
  comunicar(mensagem: string) {
    console.log(`Transmitindo mensagem: ${mensagem}`);
  }
}

@adicionarComunicacao
class NaveExploradora extends NaveBase {}

const exploradora = new NaveExploradora();
exploradora.comunicar("Contato com nova forma de vida!"); // Agora funciona!

Navegando em Direção ao Futuro

Apesar dos desafios de tipagem, os decorators de classe continuam sendo uma ferramenta poderosa para personalizar suas classes em TypeScript. À medida que o TypeScript evolui, podemos esperar que o suporte à tipagem segura em decorators se torne ainda mais robusto.

Decorators de Propriedade: Aprimorando os Atributos da Sua Nave Estelar

Assim como os engenheiros da Frota Estelar adicionam dispositivos e sensores às naves para aprimorar suas capacidades, os decorators de propriedade permitem que você adicione funcionalidades especiais aos atributos das suas classes em TypeScript.

Definição e Superpoderes

Um decorator de propriedade é como um técnico da Frota Estelar que instala um novo sensor em uma nave, permitindo que ela detecte anomalias no espaço. Ele recebe dois parâmetros: o alvo (que pode ser o construtor da classe ou o protótipo) e o nome da propriedade que está sendo decorada.

function observable(target: any, key: string): any {
  // ... (código do exemplo anterior)
}

Além de serem usados para coletar informações, os decoradores de propriedade também podem ser usados para adicionar alguns métodos ou propriedades à classe. Por exemplo, podemos escrever um decorador para adicionar a capacidade de ouvir mudanças em algumas propriedades.

function capitalizeFirstLetter(str: string) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

function observavel(target: any, key: string): any {
  // prop -> onPropChange
  const targetKey = "on" + capitalizeFirstLetter(key) + "Change";

  target[targetKey] =
    function (fn: (prev: any, next: any) => void) {
      let prev = this[key];
      Reflect.defineProperty(this, key, {
        set(next) {
          fn(prev, next);
          prev = next;
        }
      })
    };
}

class VeiculoEspacial {
  @observavel
  combustivel = 100;

  @observavel
  velocidade = 0;
}

const veiculo = new VeiculoEspacial();

veiculo.onCombustivelChange((prev, next) => console.log(`Combustível anterior: ${prev}, Combustível atual: ${next}`));
veiculo.onVelocidadeChange((prev, next) => console.log(`Velocidade anterior: ${prev}, Velocidade atual: ${next}`));

veiculo.combustivel = 80; // -> Combustível anterior: 100, Combustível atual: 80
veiculo.combustivel = 50; // -> Combustível anterior: 80, Combustível atual: 50
veiculo.velocidade = 25; // -> Velocidade anterior: 0, Velocidade atual: 25
veiculo.velocidade = 50; // -> Velocidade anterior: 25, Velocidade atual: 50
Explicação do Código:
  1. capitalizeFirstLetter: Função auxiliar que capitaliza a primeira letra de uma string.
  2. observavel: Decorador que adiciona a capacidade de escutar mudanças em uma propriedade. Ele cria um método dinâmico on<Property>Change que pode ser usado para definir um listener.
  3. VeiculoEspacial: Classe que utiliza o decorador @observavel para suas propriedades combustivel e velocidade.
  4. Uso da Classe: Criamos uma instância de VeiculoEspacial, configuramos listeners para mudanças nas propriedades combustivel e velocidade, e mudamos os valores dessas propriedades para observar as saídas dos listeners.

Esse exemplo mostra como os decoradores de propriedade podem ser usados para adicionar funcionalidades dinâmicas às classes, tornando o código mais modular e fácil de manter.

Decorators de Método: Pilotando as Ações da Sua Nave Estelar

Assim como um piloto habilidoso controla os sistemas da nave para realizar manobras precisas, os decorators de método permitem que você personalize o comportamento das ações da sua classe em TypeScript.

Definição e Superpoderes

Um decorator de método é como um copiloto que auxilia o piloto, fornecendo informações adicionais e automatizando tarefas. Ele recebe três parâmetros: o alvo (que pode ser o construtor da classe ou o protótipo), o nome do método e o descritor da propriedade, que contém detalhes sobre a implementação do método.

Os decoradores de método podem ser definidos com a seguinte anotação de tipo:

type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

Parâmetros:

  1. target: Ou a função construtora da classe para um membro estático, ou o protótipo da classe para um membro de instância.
  2. propertyKey: O nome da propriedade.
  3. descriptor: O descritor da propriedade para o membro.

Retorno:

Se um valor for retornado, ele será usado como o descritor do membro.

O que diferencia os decoradores de método dos decoradores de propriedade é o parâmetro descriptor, que nos permite substituir a implementação original e injetar alguma lógica comum. Por exemplo, podemos adicionar um logger a algum método para registrar os parâmetros de entrada e saída:

function logger(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log('Parâmetros: ', ...args);
    const resultado = original.call(this, ...args);
    console.log('Resultado: ', resultado);
    return resultado;
  }
}

class NaveEstelar {
  @logger
  calcularVelocidade(distancia: number, tempo: number) {
    return distancia / tempo;
  }
}

const enterprise = new NaveEstelar();
enterprise.calcularVelocidade(1000, 5);
// -> Parâmetros: 1000, 5
// -> Resultado: 200

Neste exemplo, o decorator logger adiciona um log automático antes e depois da execução de um método, registrando os parâmetros de entrada e o resultado.

Explicação do Código:

  1. logger: Decorador de método que intercepta a chamada do método original, registrando os parâmetros de entrada e o resultado.
  2. NaveEstelar: Classe que utiliza o decorador @logger no método calcularVelocidade.
  3. Uso da Classe: Criamos uma instância de NaveEstelar e chamamos o método calcularVelocidade. O decorador registra os parâmetros e o resultado da chamada.

Esse exemplo mostra como os decoradores de método podem ser usados para adicionar funcionalidades transversais, como logging, de uma maneira elegante e reutilizável.

Exemplo Ilustrativo: Monitorando o Sistema de Armas da Nave

Imagine que você quer monitorar o uso do sistema de armas da sua nave estelar. Com o decorator logger, você pode registrar cada disparo e o alvo atingido.

class NaveEspacial {
  @logger
  dispararArma(alvo: string) {
    console.log(`Disparando arma contra ${alvo}`);
    // ... (lógica do disparo)
  }
}

Agora, sempre que o método dispararArma for chamado, o decorator logger registrará as informações relevantes no console.

Explorando as Possibilidades

Os decorators de método oferecem um leque de possibilidades para personalizar suas classes. Você pode usá-los para:

  • Cache de resultados: Armazenar o resultado de um método para evitar cálculos repetitivos.
  • Controle de acesso: Restringir o acesso a determinados métodos com base em permissões.
  • Validação de parâmetros: Verificar se os parâmetros de um método são válidos antes de executá-lo.
  • Medição de desempenho: Medir o tempo de execução de um método para identificar gargalos.

Decorators de Acessores: Protegendo os Dados da Sua Nave Estelar

Assim como os escudos protegem uma nave estelar de ataques externos, os decorators de acessores protegem e controlam o acesso aos dados das suas classes em TypeScript.

Definição e Superpoderes

Um decorator de acessor é como um oficial de segurança da Frota Estelar que controla o acesso à ponte de comando da nave. Ele funciona de maneira semelhante a um decorator de método, mas atua sobre os métodos get e set de uma propriedade, permitindo que você personalize a forma como os dados são lidos e modificados.

Os decoradores de acessor são geralmente iguais aos decoradores de método. As únicas diferenças são as chaves no descritor:

O descritor em um decorador de método possui as chaves:

  • value
  • writable
  • enumerable
  • configurable

O descritor em um decorador de acessor possui as chaves:

  • get
  • set
  • enumerable
  • configurable
Por exemplo, podemos tornar a propriedade imutável usando um decorador:
function imutavel(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.set;

  descriptor.set = function (value: any) {
    return original.call(this, { ...value });
  }
}

class Coordenada {
  private _ponto = { x: 0, y: 0 };

  @imutavel
  set ponto(value: { x: number, y: number }) {
    this._ponto = value;
  }

  get ponto() {
    return this._ponto;
  }
}

const coordenada = new Coordenada();
const pontoInicial = { x: 1, y: 1 };
coordenada.ponto = pontoInicial;

console.log(coordenada.ponto === pontoInicial); // -> false

Esse exemplo mostra como os decoradores de acessor podem ser usados para adicionar funcionalidades específicas aos getters e setters, como garantir a imutabilidade de uma propriedade.

Explicação do Código:
  1. imutavel: Decorador de acessor que torna a propriedade imutável, garantindo que o valor setado seja uma cópia do objeto original.
  2. Coordenada: Classe que utiliza o decorador @imutavel no acessor ponto.
  3. Uso da Classe: Criamos uma instância de Coordenada e setamos a propriedade ponto. Ao comparar a propriedade com o objeto original, vemos que são diferentes, pois a propriedade foi copiada e não referenciada.

Explorando as Possibilidades

Os decorators de acessores oferecem diversas possibilidades para proteger e controlar seus dados. Você pode usá-los para:

  • Validação de dados: Verificar se os valores atribuídos a uma propriedade são válidos antes de armazená-los.
  • Formatação de dados: Transformar os dados em um formato específico antes de exibi-los.
  • Criptografia de dados: Proteger informações sensíveis com criptografia.
  • Logging de acesso: Registrar cada acesso aos dados para fins de auditoria.

Decorators de Parâmetro: Refinando os Suprimentos da Sua Nave Estelar

Assim como os oficiais de logística da Frota Estelar verificam cada item antes de embarcar em uma missão, os decorators de parâmetro permitem que você inspecione e modifique os parâmetros dos métodos da sua classe em TypeScript.

Definição e Superpoderes

Um decorator de parâmetro é como um inspetor da Frota Estelar que examina cada caixa de suprimentos antes de carregá-la na nave. Ele recebe três parâmetros: o alvo (que pode ser o construtor da classe ou o protótipo), o nome do método e o índice do parâmetro na lista de parâmetros do método.

Os decoradores de parâmetro podem ser definidos com a seguinte anotação de tipo:

type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) => void;
Parâmetros:
  1. target: Ou a função construtora da classe para um membro estático, ou o protótipo da classe para um membro de instância.
  2. propertyKey: O nome da propriedade (nome do método, não do parâmetro).
  3. parameterIndex: O índice ordinal do parâmetro na lista de parâmetros da função.

Retorno:

O valor retornado será ignorado.

Um decorador de parâmetro independente não pode fazer muita coisa. Ele é tipicamente usado para registrar informações que podem ser utilizadas por outros decoradores.

Vamos ver um exemplo onde usamos um decorador de parâmetro para registrar informações sobre os parâmetros de um método:

function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  const metadataKey = `log_${propertyKey}_parameters`;

  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(parameterIndex);
  } else {
    target[metadataKey] = [parameterIndex];
  }
}

class ComputadorEstelar {
  calcularTrajetoria(
    @logParameter velocidade: number,
    @logParameter distancia: number
  ) {
    return distancia / velocidade;
  }
}

const metadataKey = `log_calcularTrajetoria_parameters`;
const computador = new ComputadorEstelar();
const indices = computador.constructor.prototype[metadataKey];

console.log(indices); // -> [0, 1]
Explicação do Código:
  1. logParameter: Decorador de parâmetro que registra o índice dos parâmetros decorados em uma lista associada ao método.
  2. ComputadorEstelar: Classe que utiliza o decorador @logParameter nos parâmetros do método calcularTrajetoria.
  3. Uso da Classe: Criamos uma instância de ComputadorEstelar e acessamos os índices dos parâmetros decorados a partir do protótipo da classe.

Esse exemplo mostra como os decoradores de parâmetro podem ser usados para registrar informações sobre os parâmetros de um método, que podem ser utilizadas posteriormente por outros decoradores ou lógica de programação.

Explorando as Possibilidades

Os decorators de parâmetro são frequentemente usados em conjunto com outros decorators para criar soluções mais completas. Por exemplo, você pode combinar um decorator de parâmetro com um decorator de método para:

  • Registrar parâmetros: Armazenar os valores dos parâmetros para fins de depuração ou auditoria.
  • Transformar parâmetros: Converter os parâmetros para um formato diferente antes de passá-los para o método.
  • Validar parâmetros: Verificar se os parâmetros atendem a determinadas condições antes de executar o método.
  • Injetar dependências: Fornecer automaticamente valores para os parâmetros, como serviços ou outras classes.

Combinando Decorators: Uma Sinfonia de Personalização para Sua Nave Estelar

Assim como uma nave estelar complexa requer a integração de diversos sistemas para funcionar em harmonia, a combinação de diferentes tipos de decorators em TypeScript permite criar soluções sofisticadas e personalizadas para suas classes.

Exemplo Ilustrativo: Validação de Parâmetros e Métodos

Imagine que você quer garantir que os parâmetros de um método de comunicação da sua nave estelar sejam do tipo correto e que o método seja executado apenas se a nave estiver em uma determinada condição. Para isso, podemos combinar decorators de parâmetro e de método.

Passo 1: Marcar os Parâmetros

Primeiro, marcamos os parâmetros que precisam ser validados com decorators de parâmetro. Esses decorators serão executados antes dos decorators de método, permitindo que registrem informações sobre os parâmetros.

type Validator = (x: any) => boolean;

// salvar as marcações
const validateMap: Record<string, Validator[]> = {};

// 1. marcar os parâmetros que precisam ser validados
function typedDecoratorFactory(validator: Validator): ParameterDecorator {
  return (_, key, index) => {
    const target = validateMap[key as string] || [];
    target[index] = validator;
    validateMap[key as string] = target;
  }
}

function validate(_: Object, key: string, descriptor: PropertyDescriptor) {
  const originalFn = descriptor.value;

  descriptor.value = function(...args: any[]) {
    // 2. executar os validadores
    const validatorList = validateMap[key];
    if (validatorList) {
      args.forEach((arg, index) => {
        const validator = validatorList[index];
        if (validator) {
          const result = validator(arg);
          if (!result) {
            throw new Error(
              `Falha no parâmetro: ${arg} no índice: ${index}`
            );
          }
        }
      });
    }

    // 3. executar o método original
    return originalFn.call(this, ...args);
  }
}

const isInt = typedDecoratorFactory((x) => Number.isInteger(x));
const isString = typedDecoratorFactory((x) => typeof x === 'string');

class ComputadorEstelar {
  @validate
  repetirPalavra(@isString palavra: string, @isInt vezes: number) {
    return Array(vezes).fill(palavra).join(' ');
  }
}

const computador = new ComputadorEstelar();
console.log(computador.repetirPalavra('olá', 2)); // passa
console.log(computador.repetirPalavra('', 'não' as any)); // lança um erro
Explicação do Código:
  1. Validator: Um tipo que define uma função de validação.
  2. validateMap: Um mapa para armazenar os validadores associados a cada método.
  3. typedDecoratorFactory: Uma fábrica de decoradores de parâmetro que cria decoradores baseados em validadores.
  4. validate: Um decorador de método que executa os validadores antes de executar o método original. Se algum validador falhar, lança um erro.
  5. isInt e isString: Decoradores de parâmetro que validam se um valor é um inteiro ou uma string, respectivamente.
  6. ComputadorEstelar: Classe que utiliza os decoradores de parâmetro e método para validar os parâmetros do método repetirPalavra.

A combinação de decorators oferece flexibilidade para criar soluções personalizadas para suas classes. Você pode usar decorators para:

  • Logging: Registrar informações sobre a execução de métodos.
  • Cache: Armazenar resultados de métodos para evitar recálculos.
  • Autorização: Controlar o acesso a métodos com base em permissões.
  • Transações: Garantir a integridade de operações em bancos de dados.

Quando Usar Decoradores?

Ao longo desta jornada, exploramos o poder dos decorators em TypeScript e como eles podem transformar suas classes em verdadeiras naves estelares, equipadas com funcionalidades avançadas e personalizadas. Mas quando exatamente você deve usar decorators?

Os decorators são ferramentas versáteis que podem ser aplicados em diversos cenários, como:

Hooks Antes/Depois:

  • Decoradores podem ser usados para adicionar lógica que deve ser executada antes ou depois de um método específico. Isso é útil para logging, métricas, validação, entre outros.

Monitorar Mudanças de Propriedade e Chamadas de Método:

  • Com decoradores, podemos facilmente observar e reagir a mudanças de propriedades ou a chamadas de métodos, permitindo a implementação de padrões como o Observer.

Transformar Parâmetros:

  • Decoradores podem ser utilizados para transformar ou validar os parâmetros de um método antes que o método seja executado, como vimos nos exemplos de validação.

Adicionar Métodos ou Propriedades Extras:

  • Podemos usar decoradores para dinamicamente adicionar métodos ou propriedades a uma classe, aumentando sua funcionalidade de maneira modular.

Validação de Tipos em Tempo de Execução:

  • Usando metadados, podemos validar os tipos dos parâmetros e das propriedades em tempo de execução, garantindo que os dados estejam corretos e seguros.

Auto-serialização e Desserialização:

  • Decoradores podem ser utilizados para marcar propriedades que devem ser serializadas ou desserializadas automaticamente, facilitando o trabalho com APIs e armazenamento.

Injeção de Dependência:

  • Em frameworks como Angular, decoradores são amplamente utilizados para injeção de dependências, permitindo que serviços e outros recursos sejam facilmente injetados em componentes e classes.

Exemplo de Classe de Serviço com Decoradores para Logar o Resultado e Adicionar o Token à Requisição

Vamos criar um serviço que usa decoradores para logar o resultado de suas chamadas de método e adicionar um token de autenticação às requisições.

Primeiro, vamos definir os decoradores:

import { Injectable } from '@angular/core';

// Decorador para logar o resultado dos métodos
function logResult(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const result = originalMethod.apply(this, args);
    console.log(`Resultado do método ${key}:`, result);
    return result;
  }
}

// Decorador para adicionar um token à requisição
function addAuthToken(token: string): MethodDecorator {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      const modifiedArgs = args.map(arg => {
        if (typeof arg === 'object' && arg !== null) {
          return { ...arg, authToken: token };
        }
        return arg;
      });

      return originalMethod.apply(this, modifiedArgs);
    }
  }
}

// Serviço de exemplo que usa os decoradores
@Injectable({
  providedIn: 'root'
})
class ApiService {
  private authToken: string = 'my-secret-token';

  @logResult
  @addAuthToken(this.authToken)
  makeRequest(data: any) {
    // Simulação de requisição
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ success: true, data });
      }, 1000);
    });
  }
}

// Uso do serviço
const apiService = new ApiService();
apiService.makeRequest({ query: 'Angular Decorators' })
  .then(response => console.log('Resposta:', response));
Explicação do Código:
  1. logResult: Decorador de método que loga o resultado do método após sua execução.
  2. addAuthToken: Decorador de método que adiciona um token de autenticação aos argumentos da requisição. Se o argumento for um objeto, ele adiciona o token a ele.
  3. ApiService: Classe de serviço que utiliza os decoradores @logResult e @addAuthToken no método makeRequest.
  4. Uso do Serviço: Criamos uma instância de ApiService e fazemos uma requisição com o método makeRequest. O resultado é logado e o token de autenticação é adicionado ao argumento da requisição.

Este exemplo mostra como usar decoradores para adicionar funcionalidades transversais, como logging e autenticação, a métodos de serviço no Angular. Isso ajuda a manter o código modular e fácil de manter.

Decolando para Novas Aventuras

Com decorators e interceptors, você pode adicionar funcionalidades poderosas aos seus serviços Angular, como logging, autenticação, tratamento de erros e muito mais. Explore as possibilidades e descubra como essas ferramentas podem simplificar seu código, melhorar a segurança e otimizar o desempenho da sua frota estelar.

Ao longo desta jornada, exploramos o universo dos decorators em TypeScript e Angular, descobrindo como eles podem transformar suas classes e serviços em verdadeiras naves estelares, equipadas com funcionalidades avançadas e personalizadas.

Aprendemos como criar decorators de classe, propriedade, método, acessor e parâmetro, além de combinar diferentes tipos de decorators para construir soluções mais complexas. Vimos como usar metadados para obter informações sobre o seu código em tempo de design e como criar decorators personalizados para Angular, como o decorator de log e autenticação.

Com o conhecimento adquirido neste artigo, você está pronto para decolar em direção a novas aventuras no desenvolvimento de aplicações Angular. Continue explorando as possibilidades dos decorators, experimentando novas ideias e compartilhando suas descobertas com a comunidade.

Compartilhe sua Opinião!

Agora que você chegou ao fim desta jornada, gostaríamos de ouvir sua opinião. Deixe um comentário abaixo e compartilhe suas experiências com decorators em TypeScript e Angular. Quais são seus decorators favoritos? Quais desafios você enfrentou e como os superou? Quais dicas você daria para outros desenvolvedores que estão começando a usar decorators?

Sua participação é fundamental para construirmos uma comunidade de desenvolvedores Angular cada vez mais forte e colaborativa. Juntos, podemos explorar as fronteiras do desenvolvimento web e criar aplicações incríveis!