mnasyrov / ditox

Dependency injection for modular web applications
https://ditox.js.org
MIT License
92 stars 8 forks source link
container dependency dependency-container dependency-injection di injection ioc javascript module typescript

Ditox.js

lemon

Dependency injection for modular web applications

npm stars types licence coverage

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

API References

The library is available as two packages:

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="https://github.com/mnasyrov/ditox/raw/master//cdn.jsdelivr.net/npm/ditox@2.3.0/dist/umd/index.js" />
<script src="https://github.com/mnasyrov/ditox/raw/master//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

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

License

This project is licensed under the MIT license.