Could Inversify help TypeScript perform type check on an ServiceIdentifier I use with @inject decorator?

See original GitHub issue

Expected Behavior

I’d like typescript to warn me if I used @inject decorator with service ID (a symbol) pointing to something that doesn’t match property’s type.

Current Behavior

I can have a property set up with a type but I can use whatever ID I want and I will not figure it out until I’ll try to use that property in a runtime.

Possible Solution

Maybe it would be possible for Inversify to somehow support stuff like e.g. io-ts (https://github.com/gcanti/io-ts) type objects as service ids?

Context

// ...
@injectable()
class MyClass implementes IMyClass {
    constructor(
        // I should put ITokenStorage.ID here 
        // but I can put totally wrong ID here and nothing will stop me.
        // I won't figure it out until I try to use that tokenStorage in a runtime.
        @inject(ISomeDataRepository.ID) private tokenStorage: ITokenStorage,        
    ) {}
}

I keep my IDs under namespaces that share the same names as the interfaces so it helps me easily notice the mistake with my eyes but it’s still possible and easy to make such mistakes.

The same goes for binding. Yes, you provide generic typings that will catch an attempt to use wrong class with an interface specified as T (which is great) but I can still type in the wrong ID and inversify won’t notice.

It all works great (at least for binding) if I use a class as ID. I understand that interfaces disappear in runtime but using abstract classes as abstract types for services doesn’t exactly feel right (although it’s interesting how TypeScript can treat an abstract class as an implementable interface that stays in a runtime 😄 )

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:4
  • Comments:6

github_iconTop GitHub Comments

1reaction
bodograumanncommented, Mar 17, 2019

Thanks for the suggestion, I will try this out.

Another way to write it, fyi:

container.bind(TokenStorageTS2).toService(TokenStorageTS1);
0reactions
bodograumanncommented, Mar 18, 2019

What I found out:

  • You can leave away the @inject, but only if you leave it away for all the arguments. As I need to mix string service identifiers and class service identifiers, the @inject needs to stay (for now).

  • The type error message, when something goes wrong is a little confusing.

    Consider this toy example:

    import "reflect-metadata";
    import { Container, injectable, inject } from "inversify";
    
    abstract class Warrior {
      abstract fight(): string;
    }
    
    abstract class Weapon {
      abstract hit(): string;
    }
    
    abstract class ThrowableWeapon {
      abstract throw(): string;
    }
    
    @injectable()
    class Katana implements Weapon {
      public hit() {
        return "cut!";
      }
    }
    
    @injectable()
    class Ninja implements Warrior {
      public constructor(private weapon: ThrowableWeapon) {}
    
      public fight() { return this.weapon.throw(); }
    }
    
    const myContainer = new Container();
    myContainer.bind(Weapon).to(Katana);
    

    Now when we incorrectly bind Katana to ThrowableWeapon with

    myContainer.bind(ThrowableWeapon).to(Katana);
    

    we get a nice error message:

    Argument of type 'typeof Katana' is not assignable to parameter of type 'new (...args: any[]) => ThrowableWeapon'.
      Property 'throw' is missing in type 'Katana' but required in type 'ThrowableWeapon'.
    

    When we try to use toService though

    myContainer.bind(ThrowableWeapon).toService(Weapon);
    

    the error message is not helpful at all:

    Argument of type 'typeof Weapon' is not assignable to parameter of type 'string | symbol | Newable<ThrowableWeapon> | Abstract<ThrowableWeapon>'.
      Type 'typeof Weapon' is not assignable to type 'Newable<ThrowableWeapon>'.
        Cannot assign an abstract constructor type to a non-abstract constructor type.
    
  • When using bind(ThrowableWeapon).toService(Weapon), inversify does not type check Katana against ThrowableWeapon, but instead checks Weapon against ThrowableWeapon.

    So if we had a Katana that implemented both Weapon and ThrowableWeapon, we would have to use a more complex syntax to achieve proper type checking. Directly binding both to different instances of Katana would work of course. Otherwise the following does the trick:

    myContainer.bind(Katana).toSelf().inSingletonScope();
    myContainer.bind(Weapon).toConstantValue(myContainer.get(Katana));
    myContainer.bind(ThrowableWeapon).toConstantValue(myContainer.get(Katana));
    

    It is not very beautiful though.

    Luckily this case is rare.

Read more comments on GitHub >

github_iconTop Results From Across the Web

inversify/InversifyJS - Gitter
From my understanding I want make my service/manager classes be instantiated by inversify through the container instance. I can then use the inject...
Read more >
Inversify
A IoC container uses a class constructor to identify and inject its dependencies. ... You can get the latest release and the type...
Read more >
Better JavaScript? Use TypeScript and Dependency Injection
Working With TypeScript, Dependency Injection, and Discord Bots. Types and testable code are two of the most effective ways of avoiding bugs. In...
Read more >
Inversify-props: Ambiguous match found for serviceIdentifier ...
I'm using Typescript Nuxt and inversify-props to implement dependency injection throughout my application. I'm running into an issue where ...
Read more >
Dependency injection: setting up InversifyJS IoC for Typescript ...
After the container is set up the dependencies can be made injectable by importing injectable decorator from inversify and decorating ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found