Awesome
Ditox.js
<img alt="lemon" src="media/lemon.svg" width="120" />Dependency injection for modular web applications
Overview
Ditox.js is a lightweight dependency injection container for TypeScript. It provides a simple functional API to bind values and factories to container by tokens, and resolve values later. The library supports different scopes for factory bindings, including "singleton", "scoped", and "transient". Bindings can be organised as a dependency module in declarative way.
Ditox.js works with containers, tokens, values and value factories. There are no class decorators, field injectors and other magic. Explicit binding and resolving are used.
Features
- Functional API
- Container hierarchy
- Scopes for factory bindings
- Dependency modules
- Multi-value tokens
- Typescript typings
API References
The library is available as two packages:
- ditox - DI container and core tools
- ditox-react - Tools for React.js applications
Please see the documentation at ditox.js.org
Getting Started
Installation
You can use the following command to install packages:
npm install --save ditox
npm install --save ditox-react
Packages can be used as UMD modules. Use jsdelivr.com CDN site to load ditox and ditox-react:
<script src="//cdn.jsdelivr.net/npm/ditox@2.3.0/dist/umd/index.js" />
<script src="//cdn.jsdelivr.net/npm/ditox-react@2.3.0/dist/umd/index.js" />
<script>
const container = Ditox.createContainer();
// DitoxReact.useDependency(SOME_TOKEN);
</script>
Basic concepts
-
Token specifies a future injection of an "internal" implementation with a concrete "public" type.
type Logger = (message: string) => void; const LOGGER_TOKEN = token<Logger>();
-
Container keeps bindings of tokens to concrete values and implementations
const container = createContainer(); container.bindValue(LOGGER_TOKEN, (message) => console.log(message));
-
Code graph is constructed at runtime by resolving values of tokens.
const logger = container.resolve(LOGGER_TOKEN); logger('Hello World!');
Usage Examples
Binding a value
Create an injection token for a logger and DI container. Bind a logger implementation and resolve its value later in the application:
import {createContainer, token} from 'ditox';
type LoggerService = {
log: (...messages: string[]) => void;
};
// Injection token
const LOGGER_TOKEN = token<LoggerService>();
// Default implementation
const CONSOLE_LOGGER: LoggerService = {
log: (...messages) => console.log(...messages),
};
// Create a DI container
const container = createContainer();
container.bindValue(LOGGER_TOKEN, CONSOLE_LOGGER);
// Later, somewhere in the app
const logger = container.resolve(LOGGER_TOKEN);
logger.log('Hello World!');
Binding a factory
Bind a factory of a remote logger which depends on an HTTP client:
import {injectable} from 'ditox';
export type ServerClient = {
log: (...messages: string[]) => void;
sendMetric: (key: string, value: string) => void;
};
export const SERVER_CLIENT_TOKEN = token<ServerClient>();
function createLoggerClient(client: ServerClient): Logger {
return {
log: (...messages) => client.log(...messages),
};
}
container.bindFactory(
LOGGER_TOKEN,
injectable(createLoggerClient, SERVER_CLIENT_TOKEN),
);
// Later, somewhere in the app
const logger = container.resolve(LOGGER_TOKEN);
logger.log('Hello World!');
DI module
Organise related bindings and functional as a DI module:
import {bindModule, declareModule} from 'ditox';
type SendMetricFn = (key: string, value: string) => void;
const SEND_METRIC_TOKEN = token<SendMetricFn>();
function createMetricClient(client: ServerClient): Logger {
return {
sendMetric: (key: string, value: string) => client.sendMetric(key, value),
};
}
// Declare a DI module
const TELEMETRY_MODULE = declareModule<LoggerModule>({
factory: injectable((client) => {
const logger = createLoggerClient(client);
const sendMetric = (key: string, value: string) => {
logger('metric', key, value);
client.sendMetric(key, value);
};
return {logger, sendMetric};
}, SERVER_CLIENT_TOKEN),
exports: {
logger: LOGGER_TOKEN,
sendMetric: SEND_METRIC_TOKEN,
},
});
// Bind the module
bindModule(container, TELEMETRY_MODULE);
// Later, somewhere in the app
const logger = container.resolve(LOGGER_TOKEN);
logger.log('Hello World!');
const sendMetric = container.resolve(SEND_METRIC_TOKEN);
sendMetric('foo', 'bar');
Using in React app
Wrap a component tree by a DI container and bind modules:
// index.tsx
import ReactDOM from 'react-dom';
import {Greeting} from './Greeting';
import {TELEMETRY_MODULE} from './telemetry';
const APP_MODULE = declareModule({
imports: [TELEMETRY_MODULE],
});
const App: FC = () => {
return (
<DependencyContainer root>
<DependencyModule module={APP_MODULE}>
<Greeting />
</DependencyModule>
</DependencyContainer>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
Injecting a dependency by a React component:
// Greeting.tsx
import {useDependency} from 'ditox-react';
export const Greeting: FC = () => {
const logger = useDependency(LOGGER_TOKEN);
useEffect(() => {
logger.log('Hello World!');
}, [logger]);
return <>Hello</>;
};
Contact & Support
- Follow 👨🏻💻 @mnasyrov on GitHub for announcements
- Create a 💬 GitHub issue for bug reports, feature requests, or questions
- Add a ⭐️ star on GitHub and 🐦 tweet to promote the project
License
This project is licensed under the MIT license.
<!--- III. Ditox Package - Explanation of the core library - List of available methods and functions - Examples of advanced usage IV. Ditox-React Package - Explanation of the React library - List of available components and functions - Examples of usage in a React project V. Best Practices - Recommendations for using Ditox.js effectively - Tips for optimizing performance VI. Troubleshooting - Common issues and solutions - How to report bugs or request new features VII. Contributing - Guidelines for contributing to the project - Code of conduct for contributors IX. Credits - Acknowledgements for contributors and external resources used in the project. --->