typescript - Inversify inyección parcial
¿Cómo se puede hacer la inyección de propiedades parciales con Inversify?
Digamos que tenemos una clase
class MyClass {
constructor(
@inject(EXTERNAL_A) private externalA: ExternalClassA,
private b: string
) {}
}
¿Cómo puedo usar esta MyClass con inversify en otras clases en caso de que todos los valores posibles de b sean conocidos en tiempo de compilación? Entonces, solo necesito dos instancias de MyClass, una con b="a" y otra con b="b".
La única solución que encontré en este momento es definir dos vinculaciones diferentes para eso o usar una fábrica para llamar directamente a new MyClass.
En el primer caso necesito escribir algo solo así
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")
);
Parece bastante desordenado y no resuelve el siguiente problema, así como las fábricas no lo resuelven. Si tengo una jerarquía profunda de objetos en este caso, necesito construirlos todos a través de esta cadena de construcción manual de objetos.
¿Cuál es la mejor manera aquí?
Tarea con una estrella, si es posible resolver algún árbol de dependencias con el reemplazo de la misma dependencia única con la proporcionada? Como, podemos tener algo así
|-----B-----e
A|-----C-----e
|-----D
|-----e
Así que quiero reemplazar la dependencia e con la que he construido. ¿Cómo puedo lograr esto?
Respuesta #1
Puedes vincular una fábrica a tu contenedor inversify usando toFactory. Ten en cuenta que tu fábrica puede tomar argumentos.
Ver https://github.com/inversify/InversifyJS/blob/master/wiki/factory_injection.md
Aquí tienes un ejemplo específicamente para tu caso de uso.
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)
}
})
Así que estamos vinculando al identificador "MyClassFactory" una función que devuelve una instancia de MyClass dependiendo de los argumentos que le pases.
Dado que obtenemos EXTERNAL_A del contenedor a través de inversify, no lo estamos instanciando nosotros mismos, por lo que no necesitamos preocuparnos por las dependencias que tenga.
Para usar tu fábrica...
@injectable()
class SomeClass {
@inject("MyClassFactory") private myClassFactory!: (b: string) => MyClass
someMethod() {
const myClassA = this.myClassFactory('a')
const myClassB = this.myClassFactory('b')
}
}
Una cosa a tener en cuenta es que, en la pregunta, estás usando toConstantValue y por lo tanto tus clases se están construyendo y enlazando como singletons. No sé si eso es intencional, pero si lo es, aún podrías usar la fábrica anterior para hacer eso...
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')
})
También podrías crear los singletons de forma programática. Si tuvieras algún arreglo de objetos del tipo {identifier: "CLASS_A", factoryArg: 'a'}, podrías recorrerlo y crear valores dinámicos como se indicó arriba.
Con respecto a tu última pregunta, mi respuesta probablemente sea demasiado larga ahora, ¡pero revisa esta sección en la documentación, puede ayudarte! https://github.com/inversify/InversifyJS/blob/master/wiki/recipes.md#overriding-bindings-on-unit-tests
Respuesta #2
Hola,
Me temo que no hay una solución perfecta. En estos casos normalmente uso un patrón de fábrica con un método set o setup.
Ejemplo :
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;
}
}
Cuando estés configurando tu contenedor, normalmente dentro de un ContainerModule, necesitas hacer algo como esto:
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");
);
Y luego simplemente úsalo:
class MyOtherClass {
constructor(
@inject(CLASS_A) private _myInterfaceInstanceA: MyInterface,
@inject(CLASS_B) private _myInterfaceInstanceB: MyInterface) {
}
}
Espero que te ayude. Puedes encontrar más información sobre esto en: https://github.com/inversify/InversifyJS/issues/530
