TLDR: What can we do with typescript decorators, how about caching data?

The code below show an example of how we can create a decorator function that will capture the data returned from the decorated function, and then store that in our cache.

When I say store it in the cache, it will do that only if the data is NOT already in the cache!


export const CacheResponse = (log: string) => {

  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const decoratedFunction = descriptor.value.apply(this, args);

    descriptor.value = async function (...args: any[]) {
        const apiCallFunction = () => decoratedFunction.apply(this, args)
        const data = await getCachedData(createCacheKey(cacheKey, args), apiCallFunction);
        return new Promise<T>((resolve) => resolve(data) );
    };

  };

};

What else can we do with decorators?

Encapsulate any crosscutting conserns.

I want loading spinners, I also want a cache,

Can I get them both to work together?

@IsLoading(Status.LoadingEvent) @CacheResponse(“account_person”)

Encapsulate crosscutting conserns

When I first started reading about decorators, I got really confused, that is until I wrote my own and did some refactoring.

This is how I think of a decorator, it allows you to wrap a function, in another function.

Yep it’s a wrap. AOP ( Aspect orientated programming ) But what are the common use cases?

  • Logging Errors
  • Caching data.
  • IsLoading spinners

Effectivly you can run a function before the decorated function and a function after the *DF You can obtain the arguments of the DF, you can obtain the DF itself and execute it.

And example - run before

If I wanted to create a log entry everytime I call one of our API’s. I could add that code to every single one of our services

But meh, that’ just boring. What if Logging was more complex than just writing to the console log, what if I wanted the parameters to the api call. What if I wanted to log the return data.

But let’s not get ahead of ourselfs, I want a simple example.

Now everytime I call the getData method I want to logit, I also want a message with that log.


class PersonService {

  @LogIt("Get the person data")
  async getData(nino: string) {
    const conf = cache("account_person").id("nino");
    return `${nino}`;
  }

}

export const LogIt = (logMessage: string) => {

  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const decoratedFunction = descriptor.value;

    descriptor.value = async function (...args: any[]) {
        console.log(logMesssage + propertyKey)
        return decoratedFunction.apply(this, args);
    };

  };

};

How do we get our hand on the decorated function?

In the example you can see that it’s the descriptor.value, yep I’ve assigned it to decoratedFunction.

DecoratedFunction is the best term I could come up with for what it actuall is?