Partial injection in Inversify

How can I perform partial properties injection using Inversify?

Imagine we have a class like this:

class MyClass {
    constructor(
        @inject(EXTERNAL_A) private externalA: ExternalClassA,
        private b: string
    ) {}
}

Is there a way to utilize MyClass with inversify in other classes if all the possible values of b are known at compile time? Essentially, I only need instances of MyClass with b = "a" and b = "b".

The solution I have come across so far involves defining two different bindings or using a factory to directly instantiate new MyClass.

In the first scenario, I would need to do something like this:

container.bind<MyClass>(CLASS_A).toConstantValue(
    new MyClass(container.get<ExternalClassA>(EXTERNAL_A), "a")
);

container.bind<MyClass>(CLASS_B).toConstantValue(
    new MyClass(container.get<ExternalClassA>(EXTERNAL_A), "b")
);

This approach seems messy and does not address the issue of constructing deep object hierarchies manually.

What is the optimal solution here?

An advanced challenge would be if it's possible to resolve dependency trees by replacing the same single dependency with a provided one. For example, consider:

 |-----B-----e
A|-----C-----e
 |-----D
 |-----e

I want to replace the e dependency with my custom one. How can this be achieved?

Answer №1

If you want to associate a factory with your inversify container, you can do so using the toFactory method. It's worth noting that the factory can accept arguments.

For more information, visit: https://github.com/inversify/InversifyJS/blob/master/wiki/factory_injection.md

Below is an example tailored to your specific scenario.

container
    .bind<(b: string) => MyClass>("MyClassFactory")
    .toFactory<MyClass>((context: interfaces.Context) => {
      return (b: string) => {
        const classA = context.container.get<ExternalClassA>("EXTERNAL_A")

        return new MyClass(classA, b)
      }
    })

In essence, we are associating a function with identifier "MyClassFactory", which generates an instance of MyClass based on the provided arguments.

Since we fetch EXTERNAL_A from the container through inversify, we don't need to handle its dependencies ourselves.

To utilize your factory...

@injectable()
class SomeClass {
  @inject("MyClassFactory") private myClassFactory!: (b: string) => MyClass

  someMethod() {
    const myClassA = this.myClassFactory('a')
    const myClassB = this.myClassFactory('b')
  }
} 

It's important to note that in your initial setup, you utilize toConstantValue which constructs and binds classes as singletons. If this behavior is intentional, you can still employ the above factory...

container.bind<MyClass>("CLASS_A").toDynamicValue((context: interfaces.Context) => { 
  const myClassFactory = context.container.get<(b: string) => MyClass>("MyClassFactory")
  return myClassFactory('a')
})

container.bind<MyClass>("CLASS_B").toDynamicValue((context: interfaces.Context) => { 
  const myClassFactory = context.container.get<(b: string) => MyClass>("MyClassFactory")
  return myClassFactory('b')
})

You could also programmatically create singletons. For instance, if you had an array of objects like

{identifier: "CLASS_A", factoryArg: 'a'}
, you could iterate over it and generate dynamic values as demonstrated above.

In reference to your last question, I realize this response has become quite lengthy, but consider exploring this section in the documentation for potential insights: https://github.com/inversify/InversifyJS/blob/master/wiki/recipes.md#overriding-bindings-on-unit-tests

Answer №2

Greetings,

Unfortunately, there isn't a one-size-fits-all solution to this issue. In situations like these, I usually opt for a factory pattern with a set or setup method.

For instance:

interface MyInterface {
     myPublicMethod();
}

type MyInterfaceFactoryA = () => MyInterface;
type MyInterfaceFactoryB = () => MyInterface;

class MyClass extends MyInterface {

     private b: string;

     constructor(
          @inject(EXTERNAL_A) private externalA: ExternalClassA,
     ) {}

     public setup(b: string): void {
          this.b = b;
     }

}

During the container setup process, typically within a ContainerModule, you would need to do something similar to the following:

 bind<MyClass>("MyClass").to(MyClass);

 container.bind<MyInterface>(CLASS_A).toConstantValue(
      const myClass = context.container.get<MyClass>("MyClass");
      return myClass.setup("a");
 );

 container.bind<MyInterface>(CLASS_B).toConstantValue(
     const myClass = context.container.get<MyClass>("MyClass");
     return myClass.setup("a");
 );

Then, you can utilize it as follows:

 class MyOtherClass {

      constructor(
          @inject(CLASS_A) private _myInterfaceInstanceA: MyInterface,
          @inject(CLASS_B) private _myInterfaceInstanceB: MyInterface) {

      }

 }

I hope this information proves helpful. For further details, you may refer to: https://github.com/inversify/InversifyJS/issues/530

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transforming time into luxon time frames and hours

Is there a way to convert this block of code from moment.js to luxon? Here is the code snippet for reference: The following code is written using moment.js, but I would like to achieve the same functionality using luxon. timezone: null, getIn: moment() ...

Display Nested Data in Angular Material Table: A Step-by-Step Guide

I recently received a REST API response from the following URL: { "list": [ { "id": 1, "login": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7c08190f08234d3c0508521f1311"& ...

Are there any code paths that do not result in a return value?

Can anyone provide insights on the error message "Not all code paths return the value" that I'm encountering? Additionally, is there a more efficient way to utilize ES6 features instead of using forEach loops? main.ts if (rxInfos.length && rxInfos ...

NestJS TypeORM InjectRepository throwing an error: "Cannot access property 'prototype' of undefined"

Encountering an issue while trying to unit test. Here is the error message that I received: TypeError: Cannot read property 'prototype' of undefined export class UserService { constructor(@InjectRepository(User) private readonly userRepository ...

Can Aurelia components be designed to communicate values back to their parent elements?

Currently, I am developing an application using Aurelia. One part of the application features a grid that displays users along with their active status. To allow for editing of the active state, I have implemented a slide button control, similar to those ...

Narrowing types for arrays based on their discriminants

Is there a way to narrow the type string[] | number[] to one of these array types without explicitly using a function that returns something like T is number[]? I attempted this, however, TypeScript (version 5.5.4) did not comprehend it: playground declar ...

What is the best method for saving data from a reactive form to local storage?

I'm looking for a way to store reactive forms data in local storage and then access that data on another page. <form [formGroup]="testform" > firstname: <input type="text" formControlName="fname"><br> lastname: ...

Scrolling to the bottom of an ion-content in Ionic 4

I am currently developing a chat page with Ionic 4 and I'm attempting to implement an automatic scroll feature to the bottom of the page. However, the method I tried using doesn't seem to be working as expected: import { IonContent } from "@ioni ...

Unable to replicate the function

When attempting to replicate a function, I encountered a RootNavigator error Error in duplicate function implementation.ts(2393) I tried adding an export at the top but it didn't resolve the issue export {} Link to React Navigation options documen ...

I need assistance with using the angular-oauth2-oidc library to retrieve data from an asynchronous storage provider and then pass it to a synchronous storage implementation

Typically, the angular-oauth2-oidc library saves tokens in session storage by default. While you can provide your own storage provider through the OAuthStorage class, it requires a storage provider that can retrieve data synchronously. I am developing a ...

Encountering a Jest issue where the error "Unable to use import statement outside a module" is thrown when trying to import node-fetch in CommonJS format

As a newcomer to node.js, I find myself perplexed by the import/export system. When I add a package to my project's node_modules directory using NPM, should I verify whether it utilizes the ES6 module system or the CommonJS module system for exporting ...

What is the correct way to properly parse JSON attributes containing slashes?

I have 2 Custom Interfaces: DataModel.ts export interface Entry{ id: number, content: Note } export interface Note{ message: string } These interfaces are utilized in typing the HttpClient get request to fetch an array of Entries: DataService.ts getE ...

Which data structure is suitable for storing a JSON object?

Currently, I have a form set up in the following way: using (Ajax.BeginRouteForm( ... new AjaxOptions { HttpMethod = "POST", OnFailure = "OnFailure", OnSuccess ...

Empty spaces are mandatory here

When experimenting with declaring a function that must be called with a specific context (undefined, null, or global), I made an interesting discovery. I noticed that when declaring a function with this: void, it can be called with any context. However, if ...

Ways to implement a non-Typescript javascript library

Looking into the official guide on module loading in Typescript, I came across a section called "Ambient Modules" which discusses loading modules not written in Typescript. Intrigued, I decided to try implementing this method for a simple learning project. ...

Converting numbers to strings based on locale in React Native

I have a quantity that, when using the amount.toFixed() function, produces the string "100.00". I would like this result to be formatted according to the specific locale. For example, in Italian the desired output is 100,00, while in English it should be ...

Testing Functions Defined on Window Object in Jest and TypeScript: A Comprehensive Guide

I have been struggling to test a function call that is defined on the global window object. Despite reviewing various examples, I am still unable to successfully execute a simple test case. Api.ts import "./global.d"; const verifier = window.Ver ...

The type 'Object' is missing the property 'properties' for this object

I am dealing with the following code snippet: spear.ts: export class Spears { constructor( id: string = null, transaction_id: string = null, spear_id: number = null, delivery_date: any = null, delivery_time: an ...

Safari mobile and iOS app fail to activate lifecycle hooks as expected

Encountering an issue with lifecycle hooks not functioning properly in Safari mobile and iOS app. NgOnInit and ionViewDidLoad, along with other lifecycle hooks, are working on desktop and Android platforms but not on the iOS side, even when visiting the p ...

Exploring the details of an object in Ionic 4: A guide to checking elements

I am currently utilizing Ionic 4 for my project. After posting data to my API, I receive the response in the following format: { "responses": [ { "Detection": { "Images": [ {"url":"https:URL"}, ...