Sunday 8 November 2020

Decorator to call a method before any other messes with "this" context

Real problem

I think I found my real problem, inversify and everything else works fine. In the original post I omitted some part of my code because I didn't think they were causing the problem.

In my ICitiesRepo implementation I've a method ensureDb that makes sure to init some typeorm properties, since this method must be async I couldn't just call it in the constructor so should call it before any CRUD operation, then I created a decorator that has the purpose to call ensureDb before any other method in the class:

  export const ensureCall = (method: string) => {
    return (target: any) => {
      for (const prop of Object.getOwnPropertyNames(target.prototype)) {
        if (prop === method || prop === 'constructor') continue;

        const originalMethod = target.prototype[prop];
        if (originalMethod instanceof Function) {
          target.prototype[prop] = async (...args: any[]) => {
            await target.prototype[method]();
            return originalMethod.apply(this, args);
          };
        }
      }
    };
  };

And this is the use:

   @ensureCall('ensureDb')
   @injectable()
   class CitiesRepo implements ICitiesRepo {
       @inject('CitiesWriteRepo') private readonly repo: IWriteRepo<City>;
       
       async ensureDb() {
          await this.repo.doSomething();
          // repo is undefined because I messed up with the context
       }
   
       // interface implementation
   }

If I remove that decorator and just call ensureDb before others method it works:

   const container = getContainer();
   const citiesRepo: ICitiesRepo = container.get('CitiesRepo');
   await citiesRepo.ensureDb();
   const list = await citiesRepo.getAll(); // It works

Is it possible to fix this issue or some other better way to achieve what I want to do?

Original post

I have a project with a generic repository but I'm having some problem to inject the repository object with inversifyJS. The structure is like this:

   interface IWriteRepo<T>  { /* interface members */ }

   @injectable()
   class WriteRepo<City> implements IWriteRepo<City> { /* interface implementation */ }

   interface ICitiesRepo { /* interface members */ }

   @injectable()
   class CitiesRepo implements ICitiesRepo {
       @inject('CitiesWriteRepo') private readonly repo: IWriteRepo<City>;

       // interface implementation
   }

   interface ICitiesService { /* interface members */ }

   @injeactable()
   class CitiesService {
      @inject('CitiesRepo') repo: ICitiesRepo;

       // interface implementation
   }

   // binding
   const container = new Container();
   container.bind<IWriteRepo<City>>('CitiesWriteRepo').to(WriteRepo);
   container.bind<ICitiesRepo>('CitiesRepo').to(CitiesRepo);
   container.bind<ICitiesService>('CitiesService').to(CitiesService);

I'm testing the code with a node application and when I try to get the generic repository (IWriteRepo<City>) and CitiesRepo I don't have any problem:

   const container = getContainer();
   const citiesRepo: ICitiesRepo = container.get('CitiesRepo'); // OK and prop repo injected correctly
   const citiesWriteRepo: IWriteRepo<City> = container.get('CitiesWriteRepo'); // OK

The problem comes when I try to get the service:

   const container = getContainer();
   const citiesService: ICitiesService = container.get('CitiesService');

The service is injected correctly and also repo: ICitiesRepo but the generic "grand child" repo: IWriteRepo<City> is undefined.

Any idea about how to fix it?

EDIT

I'm still trying to find out what is the problem. Now I tried to inject the class into the constructor and something strange happens: InversifyJS injects the dependency correctly, I assign it to the property but it's undefined when I try to use it.

   interface ICitiesRepo { /* interface members */ }

   @injectable()
   class CitiesRepo implements ICitiesRepo {
       private readonly repo: IWriteRepo<City>;

       constructor(@inject('CitiesWriteRepo') repo: IWriteRepo<City>) {
          console.log(repo); // result: WriteRepo {}
          this.repo = repo;
          console.log(this.repo); // result: WriteRepo {}
       }

       getAll() {
          console.log(this.repo); // result: undefined
       }

       // interface implementation
   }


from Decorator to call a method before any other messes with "this" context

No comments:

Post a Comment