I am wondering how would you use typescript IOC specifically node app.
In case of external module-based architecture there is no any classes in the app. Just pure modules because my app heavily depends on node_modules.
How would I integrate IOC solution in such case? Any thoughts?
Here is my specific case I want to use IOC for:
I have mongoose model:
interface IStuffModel extends IStuff, mongoose.Document { }
var Stuff= mongoose.model<IStuffModel>('Stuff', Schemas.stuffSchema);
export = Stuff;
And related fake class:
export class Stuff implements IStuff {
//do stuff
}
How would I integrate IOC solution in such case
Here is a very popular library that I recommend : https://github.com/inversify/InversifyJS
External modules
Using external modules doesn't change the code at all. Instead of
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", FooBar));
Production
You just have
import {ProdFooBar} from "./prodFooBar";
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", ProdFooBar));
Test
import {MockFooBar} from "./mockFooBar";
kernel.bind(new TypeBinding<FooBarInterface>("FooBarInterface", MockFooBar));
As Basarat indicated in his answer, I have developed an IoC container called InversifyJS with advanced dependency injection features like contextual bindings.
You need to follow 3 basic steps to use it:
1. Add annotations
The annotation API is based on Angular 2.0:
import { injectable, inject } from "inversify";
#injectable()
class Katana implements IKatana {
public hit() {
return "cut!";
}
}
#injectable()
class Shuriken implements IShuriken {
public throw() {
return "hit!";
}
}
#injectable()
class Ninja implements INinja {
private _katana: IKatana;
private _shuriken: IShuriken;
public constructor(
#inject("IKatana") katana: IKatana,
#inject("IShuriken") shuriken: IShuriken
) {
this._katana = katana;
this._shuriken = shuriken;
}
public fight() { return this._katana.hit(); };
public sneak() { return this._shuriken.throw(); };
}
2. Declare bindings
The binding API is based on Ninject:
import { Kernel } from "inversify";
import { Ninja } from "./entities/ninja";
import { Katana } from "./entities/katana";
import { Shuriken} from "./entities/shuriken";
var kernel = new Kernel();
kernel.bind<INinja>("INinja").to(Ninja);
kernel.bind<IKatana>("IKatana").to(Katana);
kernel.bind<IShuriken>("IShuriken").to(Shuriken);
export default kernel;
3. Resolve dependencies
The resolution API is based on Ninject:
import kernel = from "./inversify.config";
var ninja = kernel.get<INinja>("INinja");
expect(ninja.fight()).eql("cut!"); // true
expect(ninja.sneak()).eql("hit!"); // true
The latest release (2.0.0) supports many use cases:
Kernel modules
Kernel middleware
Use classes, string literals or Symbols as dependency identifiers
Injection of constant values
Injection of class constructors
Injection of factories
Auto factory
Injection of providers (async factory)
Activation handlers (used to inject proxies)
Multi injections
Tagged bindings
Custom tag decorators
Named bindings
Contextual bindings
Friendly exceptions (e.g. Circular dependencies)
You can learn more about it at https://github.com/inversify/InversifyJS
In the particular context of Node.js there is a hapi.js example that uses InversifyJS.
Related
I am writing a node app in typescript. I have written a class decorator #myDecorator, and the purpose of #myDecorator is such that I need to keep track of all the classes to which it's applied.
My question: how do I make sure all of those decorated classes are loaded before making use of that behavior? Some example code will help to make this more concrete:
Declaration of the decorator in file myDecorator.ts:
type ConstructorType = { new (...args: any[]): any };
// keep track of decorated classes
const registeredClasses: Map<string, ConstructorType> = new Map();
// class decorator
export function myDecorator<T extends ConstructorType>(targetConstructor: T) {
// create the modified class
const newClass = class extends targetConstructor { /* ... */ }
// register the modified class
registeredClasses.set(targetConstructor.name, newClass);
// and return it
return newClass;
}
export function doSomethingWithMyDecoratedClasses() {
//... some behavior that relies on `registeredClasses`
}
Declaration of a decorated class in file someClass.ts
import {myDecorator} from 'myDecorator.ts'
#myDecorator
class SomeClass { /* ... */ }
Making use of doSomethingWithMyDecoratedClasses in anotherFile.ts:
import { doSomethingWithMyDecoratedClasses } from 'myDecorator.ts`
//...
doSomethingWithMyDecoratedClasses()
//...
The problem is that I need to make sure that SomeClass has been added to registeredClasses before I make this call to doSomethingWithMyDecoratedClasses. And, in fact, I've written a number of such classes in my app, and I need to make sure they are all registered.
My current best understanding is that I need to call import 'someClass.ts' in anotherFile.ts (and, in fact, import all files where decorated classes are declared), so really I need to import someClass1.ts, import someClass2.ts, ...
Is that the best/only approach? Is there a recommended pattern for doing so?
Most applications have an index file that is responsible for importing the top level things. If you import doSomethingWithMyDecoratedClasses there, you'll guarantee that everything else is imported first.
An alternative would be to not call it in the root level of a module, and instead wait for an event.
I'm currently working through the database integration docs for NestJS using TypeOrm. In these docs there are examples that show how to inject a custom database repository using the app.module from NestJS. All of these examples inject classes using the actual type of the custom repository.
#Injectable()
export class AuthorService {
constructor(private authorRepository: AuthorRepository) {}
}
This code is injected via the app.modules by providing a import like such:
#Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
This works well if you are fine with programming against an implementation, but I prefer to use an interface in my classes. I've already found the solution to injecting classes via an interface with NestJS in a previous question, but when I try to inject my custom repository like that, it doesn't seem to instanciate correctly and becomes undefined.
(node:16658) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'save' of undefined
Because of this, I assume you can only inject customRepositories via the forFeature() call in the app.module, but that won't allow me to use interfaces for injection, as far as I know. Is there any other way I can inject a custom TypeOrm repository without having the replace all my interfaces for the implementation of my custom repository? Thanks in advance!
Edit
Here is my current code, I managed to get it to inject, but this still forces me to use the implementation instead of the interface each time I call the constructor. This is mainly an issue when testing due to mocking.
#CommandHandler(FooCommand)
export class FooHandler
implements ICommandHandler<FooCommand> {
private fooRepository: IFooRepository; // Using Interface as a private property.
private barEventBus: IEventBus;
constructor(fooRepository: FooRepository,
barEventBus: EventBus) { // Forced to use implementation in constructor for injection.
this.fooRepository = fooRepository;
this.barEventBus = barEventBus;
}
#EntityRepository(FooEntity)
export class FooRepository extends Repository<FooEntity> implements IFooRepository {
getFoo() {
// Do stuff
}
}
#Module({
imports: [TypeOrmModule.forRoot(), TypeOrmModule.forFeature([FooRepository]],
// Other module setup
})
export class AppModule {}
It should work with using the InjectRepository decorator where you specify the Repository but then you type is as your interface instead and when testing you just provide the IFooRepository!
Example code:
constructor(#InjectRepository(FooRepository) fooRepository: IFooRepository,
barEventBus: EventBus) {
Edit: This answer is crap, that abstract-class-as-interface hack used does not work out as the defined methods seem to be optional to implement despite being marked as abstract.
Well, kind of got it working. Based on this answer https://stackoverflow.com/a/74561702/913136 I used an abstract class as interface (you can actually implement it) for not being required to pass strings around as tokens. Only drawback is the misuse of the abstract class. Not sure yet if I like it.
Using an actual interface in the same way seems not to be possible unfortunately. Urgh.
#Module({
imports: [
TypeOrmModule.forRoot({
...dataSource.options,
autoLoadEntities: true,
}),
TypeOrmModule.forFeature([Listing]),
],
controllers: [ViewListingController],
providers: [
{
provide: ListingRepository,
useClass: TypeOrmListingRepository,
},
],
})
makeshift interface:
import { Listing } from "./Listing";
export abstract class ListingRepository {
abstract findMostRecent: () => Promise<Listing[]>;
}
implementation:
import { Listing, ListingRepository } from "../../Domain";
import { Injectable } from "#nestjs/common";
import { Repository, DataSource } from "typeorm";
#Injectable()
export class TypeOrmListingRepository
extends Repository<Listing>
implements ListingRepository
{
constructor(private dataSource: DataSource) {
super(Listing, dataSource.createEntityManager());
}
findMostRecent() {
return this.find({});
}
}
import { Controller, Get } from "#nestjs/common";
import { ListingRepository } from "../Domain";
#Controller("listings")
export class ViewListingController {
constructor(private readonly listingRepo: ListingRepository) {}
#Get("/most-recent")
listMostRecent() {
return this.listingRepo.findMostRecent();
}
}
I'm using tapestry 5.4.1. I have a component with a module that requires prototype. I know prototype is available in the core stack. However how do I import this as a dependency.
My module:
define(["prototype"], function(container, link) {
return new Ajax.PeriodicalUpdater(container, link, {
method : 'post', frequency : 5, decay : 1
});
});
I tried adding this to the class but the path cannot be resolved
#Import(library = {"prototype.js"})
public class Update {
Tried injecting the asset and adding it to the environmental javascriptsupport but it somehow looks for it in the wrong location.
#Inject
#Path("classpath:META-INF/assets/tapestry5/prototype.js")
private Asset prototype;
and
javascriptSupport.importJavaScriptLibrary(prototype);
javascriptSupport.require("update").with(container, getLink());
I don't want to hard code the url with the generated hash.
/assets/meta/z67bxxxx/tapestry5/scriptaculous_1_9_0/prototype.js
Anything I am missing here? Any help would be appreciated.
Make sure you define correct infrastructure in your AppModule
#ApplicationDefaults
public static void contributeApplicationDefaults(MappedConfiguration<String, Object> configuration) {
configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "prototype");
}
You don't have to specify dependency clearly ["prototype"].
I am using inversify for an mean stack application developed with typescript. Following the instructions here at this url: https://www.npmjs.com/package/inversify, I created the inversify.config.ts file and added the code relevant to my needs. I am receiving the following error for one of my binding:
"Error:(39, 71) TS2349:Cannot invoke an expression whose type lacks a call signature. Type 'typeof ExampleRepository' has no compatible call signatures.".
inversify.config.ts:
myContainer.bind<IExampleRepository<IGroup>>(TYPES.IExampleRepository).to(ExampleRepository<IGroup>).whenTargetNamed("exampleRepository");
types.ts:
IExampleRepository: Symbol("ExampleRepository")
How would the inversify.config.ts entry have to change to accomodate this need? What am I doing wrong here? Can inversify handle this scenario?
I think that if your interface is generic IExampleRepository<T> then your ExampleRepository doesn't need the <IGroup> generic on it.
import { Container, injectable } from "inversify";
const TYPES = {
IExampleRepository: Symbol("IExampleRepository"),
};
class IGroup {
}
interface IExampleRepository<T> {
group: T;
}
#injectable()
class ExampleRepository implements IExampleRepository<IGroup> {
group: IGroup
}
const myContainer = new Container();
myContainer.bind<IExampleRepository<IGroup>>(TYPES.IExampleRepository).to(ExampleRepository).whenTargetNamed("exampleRepository");
`
Please provide more example code for IExampleRepository and Examplerepository. That might help get a better answer.
I'm looking for a way to get translation text without using FormattedMessage. Until now, I've found only this solution that provides to use ContextTypes an React's EXPERIMENTAL feature. Are there others ways to accomplish this (or other library/npm module)?
I prefer using context, but react-intl does also provide a higher order component injectIntl you can use instead. This will pass a prop intl that has all the imperative formatting functions.
import React from "react";
import {injectIntl, intlShape} from "react-intl";
class MyComponent extends React.Component {
static propTypes = {
intl: intlShape.isRequired
}
render() {
return <p>{this.props.intl.formatDate(new Date())}</p>;
}
}
export default injectIntl(Component);