Brandi Dependency Injection

repository·main·Indexed 42 days ago

https://github.com/vovaspace/brandi

A lightweight, strongly-typed, framework-agnostic dependency injection container for TypeScript. It is decorator-free and supports hierarchical containers. Includes brandi-react for React 16.8+ integration, featuring the ContainerProvider component, useInjection hook, createInjectionHooks utility, and a tagged HOC for conditional bindings.

Tokens
22.6K
Snippets
83
Records
104
Agent score
51%

What's inside Brandi

  1. Overview of Brandi dependency injection

    main

    Brandi is a lightweight, framework-agnostic dependency injection (DI) container designed for TypeScript. It implements Inversion of Control (IoC) by moving the creation and binding of dependent objects outside of the classes that require them.

    Key features include:

    • Framework agnostic: Compatible with any UI or server framework.
    • Lightweight: Designed for maximum performance with a tiny footprint.
    • Strongly typed: Provides out-of-the-box TypeScript support.
    • Decorators free: Does not require experimentalDecorators in tsconfig.json or a Reflect polyfill.
  2. What is a DependencyModule and how does it differ from a Container?

    main

    A DependencyModule is a logical space used to organize bindings. It is similar to a Container, but with a key distinction: a DependencyModule can only store dependencies; it cannot resolve or inject them.

    To use the bindings stored in a module, you must import them into a Container using the .use(...tokens).from(module) method. This allows you to group related dependencies (like an API service layer) and compose them into a main application container.

    import { DependencyModule, Container, token } from 'brandi';
    
    const myModule = new DependencyModule();
    const container = new Container();
    
    // Bind in module
    myModule.bind(token('myToken')).toConstant('value');
    
    // Use in container
    container.use(token('myToken')).from(myModule);
  3. Build hierarchical DI systems with Container.extend()

    main

    Brandi supports hierarchical Dependency Injection (DI) where containers can have parent containers. This allows you to create a tree of containers where dependencies bubble up the hierarchy.

    How it works:

    1. When you request a dependency from a container using .get(), the container first checks its own bindings.
    2. If the dependency is not found, the request is passed to its parent container.
    3. This process continues up the chain until a container satisfies the request or the top of the hierarchy is reached.

    To create a child container that inherits from a parent, use the .extend(parent) method on a new Container instance.

    import { Container, token } from 'brandi';
    
    class ApiService {}
    
    const TOKENS = {
      apiService: token<ApiService>('apiService'),
    };
    
    const parentContainer = new Container();
    
    parentContainer
      .bind(TOKENS.apiService)
      .toInstance(ApiService)
      .inTransientScope();
    
    // Create a child container that inherits from parentContainer
    const childContainer = new Container().extend(parentContainer);
    
    // The child container will find the binding in the parent
    const apiService = childContainer.get(TOKENS.apiService);
    
    expect(apiService).toBeInstanceOf(ApiService);
  4. Use the `Container` for dependency management

    main

    The Container is the heart of Brandi. You can create one using createContainer() and then use it to manage the lifecycle and binding of your dependencies.

    Key capabilities include:

    • Binding: Using .bind(token) to associate a token with a constant, a factory, or an instance.
    • Resolution: Using .get(token) to retrieve a resolved dependency.
    • Cloning & Extending: Using .clone() to create a snapshot or .extend(container) to create a child container that inherits bindings.
    • Module Integration: Using .use(...tokens) to import dependencies from a DependencyModule.
  5. Use the Container class to manage dependencies

    main

    The Container class is the core of Brandi. It is used to store dependency information, resolve dependencies, and inject them into targets. You can instantiate it using new Container() or the createContainer() alias.

    import { Container, createContainer } from 'brandi';
    
    const container = new Container();
    // or
    const container = createContainer();
  6. Core API components of Brandi

    main

    Brandi's API is organized around four primary concepts:

    1. Container: The central registry where dependencies are bound and resolved. It provides methods for binding values, capturing state, and extending functionality.
    2. Dependency Modules: Abstractions used to group and manage sets of dependencies, often used with use() to load multiple tokens into a container at once.
    3. Pointers: Unique identifiers used to represent dependencies within the container. These include token (for specific dependencies) and tag (for categorizing or metadata).
    4. Registrators: Decorators or functions used to mark targets (like classes or functions) as requiring specific dependencies (injected) or having specific metadata (tagged).
  7. TypeScript and Runtime Requirements

    main

    TypeScript

    TypeScript type definitions are included in the brandi package; no separate @types/brandi installation is required.

    Runtime Globals

    Brandi has no external dependencies, but requires the following globals to be present in the environment:

    • Symbol
    • WeakMap

    Build Targets

    Brandi provides precompiled CommonJS and ESModule builds targeting ES2018. Additionally, esnext, esnext:main, and esnext:module fields are available for ESNext builds.

  8. Implement hierarchical DI with nested ContainerProviders

    main

    You can create a hierarchical DI system by nesting ContainerProvider components. A module's ContainerProvider will automatically access and extend the root container from a parent provider. This allows module-specific components to request dependencies (like TOKENS.apiService) and resolve them from the parent container, while still being able to resolve module-specific dependencies from their own local container.

    Example: Providing a Module's Own Container

    import { createContainer } from 'brandi';
    import { ContainerProvider } from 'brandi-react';
    import { FunctionComponent } from 'react';
    
    import { TOKENS } from '../tokens';
    import { UserService } from './UserService';
    import { UserComponent } from './UserComponent';
    
    const container = createContainer();
    
    // This binding is local to the UserModule
    container.bind(TOKENS.userService).toInstance(UserService).inTransientScope();
    
    export const UserModule: FunctionComponent = () => (
      <ContainerProvider container={container}>
        <UserComponent />
      </ContainerProvider>,
    );
    import { createContainer } from 'brandi';
    import { ContainerProvider } from 'brandi-react';
    import { FunctionComponent } from 'react';
    
    import { TOKENS } from '../tokens';
    import { UserService } from './UserService';
    import { UserComponent } from './UserComponent';
    
    const container = createContainer();
    
    container.bind(TOKENS.userService).toInstance(UserService).inTransientScope();
    
    export const UserModule: FunctionComponent = () => (
      <ContainerProvider container={container}>
        <UserComponent />
      </ContainerProvider>,
    );
  9. How Brandi handles dependency injection

    main
    Brandi exclusively uses Constructor Injection to manage dependencies. It does not support other DI patterns (such as property or setter injection). In this model, a class declares its dependencies through its constructor, and the Brandi DI container is responsible for providing the required instances during instantiation.
  10. Install Brandi-React bindings

    main

    To use Brandi within React components, install brandi-react. This requires React 16.8 or later and the core brandi package to be installed as well.

    # NPM
    npm install brandi-react
    
    # Yarn
    yarn add brandi-react
  11. Deploy the Brandi website to GitHub Pages

    main

    You can deploy the website to GitHub Pages by running the yarn deploy command. This command builds the site and pushes it to the gh-pages branch. You must provide your GitHub username via the GIT_USER environment variable and set USE_SSH=true.

    GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy