Injection Types
All the ways to define and inject dependencies in NeoSyringe.
Overview
| Type | Syntax | Use Case |
|---|---|---|
| Class | { token: MyClass } | Simple autowiring |
| Interface | { token: useInterface<IFoo>(), provider: Foo } | Abstraction |
| Explicit | { token: MyClass, provider: OtherClass } | Override default |
| Factory | { token: X, provider: (c) => ... } | Dynamic creation |
| Value | { token: X, useValue: myValue } | Pre-built objects |
| Property | { token: useProperty(Class, 'param') } | Primitives |
| Multi | { token: X, provider: Y, multi: true } | Plugin / strategy lists |
Class Token
Register a class directly. Dependencies are resolved automatically from constructor parameters.
class Repository {
findAll() { return []; }
}
class Service {
constructor(private repo: Repository) {}
}
export const container = defineBuilderConfig({
injections: [
{ token: Repository },
{ token: Service } // Repository injected automatically
]
});Interface Token
Bind an interface to a concrete implementation using useInterface<T>().
import { useInterface } from '@djodjonx/neosyringe';
interface ICache {
get(key: string): any;
set(key: string, value: any): void;
}
class RedisCache implements ICache {
get(key: string) { /* ... */ }
set(key: string, value: any) { /* ... */ }
}
class MemoryCache implements ICache {
private store = new Map();
get(key: string) { return this.store.get(key); }
set(key: string, value: any) { this.store.set(key, value); }
}
// Production
const prodContainer = defineBuilderConfig({
injections: [
{ token: useInterface<ICache>(), provider: RedisCache }
]
});
// Development
const devContainer = defineBuilderConfig({
injections: [
{ token: useInterface<ICache>(), provider: MemoryCache }
]
});How It Works
At compile-time, useInterface<ICache>() is replaced with a unique string ID:
// Before (your code)
{ token: useInterface<ICache>(), provider: RedisCache }
// After (generated)
{ token: "ICache", provider: RedisCache }Explicit Provider
Override which class is used when resolving a token:
class UserService {
validate(user: User) { /* production logic */ }
}
class TestUserService extends UserService {
validate(user: User) { return true; } // Skip validation in tests
}
// Test container
const testContainer = defineBuilderConfig({
injections: [
{ token: UserService, provider: TestUserService }
]
});
const service = testContainer.resolve(UserService);
// service instanceof TestUserService === trueFactory Provider
Use factory functions when you need:
- Dynamic configuration
- Container access for conditional resolution
- External resource initialization
Arrow Function (Auto-detected)
{
token: useInterface<IConfig>(),
provider: () => ({
apiUrl: process.env.API_URL,
environment: process.env.NODE_ENV
})
}Factory with Container Access
{
token: useInterface<IHttpClient>(),
provider: (container) => {
const config = container.resolve(useInterface<IConfig>());
const logger = container.resolve(useInterface<ILogger>());
return new HttpClient({
baseUrl: config.apiUrl,
logger,
timeout: config.timeout
});
}
}Explicit Factory Flag
For regular functions that aren't arrow functions:
function createDatabaseConnection(container: Container) {
const config = container.resolve(useInterface<IDbConfig>());
return new DatabaseConnection(config.connectionString);
}
{
token: useInterface<IDatabase>(),
provider: createDatabaseConnection,
useFactory: true // Required for non-arrow functions
}Value Provider
Register a pre-built value directly — no class, no factory. The value is embedded in the generated container as-is.
interface DatabaseConfig {
url: string;
poolSize: number;
}
const dbConfig: DatabaseConfig = {
url: process.env.DB_URL ?? 'localhost:5432',
poolSize: 10
};
export const container = defineBuilderConfig({
injections: [
{ token: useInterface<DatabaseConfig>(), useValue: dbConfig }
]
});
const config = container.resolve(useInterface<DatabaseConfig>());
// Returns the exact dbConfig object — always the same instanceAlways Singleton
Values are always singletons. Each resolve() call returns the same object.
Primitives Not Supported
useValue does not accept primitive types (string, number, boolean). Use useProperty for injecting primitive configuration values into class constructors.
Property Token
Inject primitive values (string, number, boolean) into class constructors.
The Problem
How do you inject configuration values without polluting your classes?
// ❌ Coupled to DI
class ApiService {
constructor(@inject('API_URL') private apiUrl: string) {}
}
// ❌ Coupled to environment
class ApiService {
private apiUrl = process.env.API_URL;
}The Solution
Use useProperty<T>(Class, 'paramName'):
import { useProperty } from '@djodjonx/neosyringe';
// ✅ Pure class
class ApiService {
constructor(
private apiUrl: string,
private timeout: number,
private retryCount: number
) {}
}
// Property tokens
const apiUrl = useProperty<string>(ApiService, 'apiUrl');
const timeout = useProperty<number>(ApiService, 'timeout');
const retryCount = useProperty<number>(ApiService, 'retryCount');
export const container = defineBuilderConfig({
injections: [
{ token: apiUrl, provider: () => 'https://api.example.com' },
{ token: timeout, provider: () => 5000 },
{ token: retryCount, provider: () => 3 },
{ token: ApiService } // All primitives injected!
]
});Scoped to Class
Property tokens are scoped to their class, avoiding collisions:
const serviceAUrl = useProperty<string>(ServiceA, 'url');
const serviceBUrl = useProperty<string>(ServiceB, 'url');
// serviceAUrl !== serviceBUrl (different tokens!)Validated by LSP
The IDE plugin validates property names:
const invalid = useProperty<string>(ApiService, 'invalidParam');
// 🔴 Error: Parameter 'invalidParam' does not exist in ApiService constructorMulti Registration
Register multiple providers for the same token. Resolve them all at once with resolveAll().
import { defineBuilderConfig, useInterface } from '@djodjonx/neosyringe';
interface IPlugin {
execute(): void;
}
class AuthPlugin implements IPlugin {
execute() { /* check auth */ }
}
class LogPlugin implements IPlugin {
execute() { /* log request */ }
}
class MetricsPlugin implements IPlugin {
execute() { /* record metrics */ }
}
export const container = defineBuilderConfig({
injections: [
{ token: useInterface<IPlugin>(), provider: AuthPlugin, multi: true },
{ token: useInterface<IPlugin>(), provider: LogPlugin, multi: true },
{ token: useInterface<IPlugin>(), provider: MetricsPlugin, multi: true },
]
});
// Resolve all registered providers
const plugins = container.resolveAll(useInterface<IPlugin>());
plugins.forEach(p => p.execute());Use multi: true for plugin systems, middleware chains, and event handlers — anywhere you need an open-ended list of contributors for the same interface.
Order Preserved
Providers are resolved in registration order.
Cannot Mix
A token registered with multi: true cannot also be registered without it (and vice versa). NeoSyringe reports an error at build time.
Combined Example
import { defineBuilderConfig, useInterface, useProperty } from '@djodjonx/neosyringe';
// Interfaces
interface ILogger { log(msg: string): void; }
interface IHttpClient { get(url: string): Promise<any>; }
// Implementations
class ConsoleLogger implements ILogger {
log(msg: string) { console.log(msg); }
}
// Pure service class
class ApiService {
constructor(
private logger: ILogger,
private http: IHttpClient,
private baseUrl: string,
private timeout: number
) {}
}
// Tokens
const baseUrl = useProperty<string>(ApiService, 'baseUrl');
const timeout = useProperty<number>(ApiService, 'timeout');
export const container = defineBuilderConfig({
name: 'AppContainer',
injections: [
// Interface bindings
{ token: useInterface<ILogger>(), provider: ConsoleLogger },
// Factory for complex initialization
{
token: useInterface<IHttpClient>(),
provider: (container) => {
const logger = container.resolve(useInterface<ILogger>());
return new HttpClient(logger);
}
},
// Primitive values
{ token: baseUrl, provider: () => process.env.API_URL ?? 'http://localhost' },
{ token: timeout, provider: () => 5000 },
// Service with mixed dependencies
{ token: ApiService }
]
});