Skip to main content

Basic Dependency Injection

Reactant provides dependency injection, which supports TypeScript(also supports JavaScript), and we recommend the experimentalDecorators feature based on TypeScript, as well as Reflect.metadata to record the dependency injection metadata.

To enable experimental support for decorators, you must enable the experimentalDecorators compiler option either on the command line or in your tsconfig.json:

{
"compilerOptions": {
"experimentalDecorators": true
}
}

@injectable()

Use @injectable(), Reactant to turn the current module into an injectable module.

@injectable()
class Foo {}

@injectable()
class Bar {
constructor(public foo: Foo) {}
}

If it does not depend on any other module, in fact, the @injectable() modifier of the current module can be omitted, and Reactant turns it into an injectable module and injects it automatically when the createApp runs.

@injectable() for JavaScript

Since JavaScript has no better features to support the handling of dependency injections similar to TypeScript decorators, using Reactant's dependency injections in JavaScript can only be done using @injectable() with the information of the dependency injection.

@injectable()
class Foo {}

@injectable({
deps: [Foo],
})
class Bar {
constructor(foo) {}
}

See @injectable() API doc for more information.

Be sure to install @babel/plugin-propose-decorators and configure the babel settings correctly.

@inject()

Use @inject() and bring its corresponding identifier parameter as dependency injection management.

interface Bar {
text: string;
}

@injectable()
class Foo {
constructor(@inject('Bar') public bar: Bar) {}

get text() {
return this.bar.text;
}
}

It does NOT support JavaScript, and the equivalent is written as:

@injectable({
deps: [{ provide: 'Bar' }],
})
class Foo {
constructor(bar) {
this.bar = bar;
}

get text() {
return this.bar.text;
}
}

When you need to depend on injecting multiple instances of the same class, you can consider using different injection tokens, for example

@injectable()
class Bar {}

@injectable()
class Foo {
constructor(@inject('Bar0') public bar: Bar) {}
}

@injectable()
class AppView extends ViewModule {
constructor(@inject('Bar1') public bar: Bar, public foo: Foo) {}
}

const app = createApp({
main: AppView,
modules: [
{
provide: 'Bar0',
useClass: Bar,
},
{
provide: 'Bar1',
useClass: Bar,
},
]
render,
});

expect(app.instance.bar !== app.instance.foo.bar).toBe(true);

AppView dependency bar is a different instance from Foo dependency bar

@optional()

Use optional() with a dependency identifier that you can use to inject an optional module.

If you only need yourself as a dependency identifier, then you can abbreviate @optional(Bar) public bar?: Bar to @optional() public bar?: Bar.

interface Bar {
text: string;
}

@injectable()
class Foo {
constructor(@optional('Bar') public bar?: Bar) {}

get text() {
return this.bar?.text;
}
}

It does NOT support JavaScript, and the equivalent is written as:

@injectable({
deps: [{ provide: 'Bar', optional: true }],
})
class Foo {
constructor(bar) {
this.bar = bar;
}

get text() {
return this.bar === null || this.bar === undefined
? undefined
: this.bar.text;
}
}