npm package: @hooked74/react-di
Description: Dependency injection for react based upon inversify.
Author: Igor Novikov
Latest version: 1.1.5

<h1 align="center"><strong>React Di</strong></h1>

[![Build Status](https://travis-ci.org/Hooked74/react-di.svg?branch=master)](https://travis-ci.org/Hooked74/react-di)
[![npm](https://img.shields.io/npm/v/@hooked74/react-di)](https://www.npmjs.com/package/@hooked74/react-di)
[![License](https://img.shields.io/npm/l/@hooked74/react-di)](https://github.com/Hooked74/react-di/blob/master/LICENSE)
[![Module Size](https://img.shields.io/badge/dynamic/json?color=success&label=module%20size&query=%24.module&url=https%3A%2F%2Fraw.githubusercontent.com%2FHooked74%2Freact-di%2Fmaster%2F.size-snapshot.json)](https://github.com/Hooked74/react-di/blob/master/.size-snapshot.json)

## Table of contents

<!--ts-->

- [Overview](#overview)
- [Install](#install)
- [Usage](#usage)
  - [Getting started](#getting-started)
  - [Injection](#injection)
    - [Injection from class references](#injection-from-class-references)
    - [Injection via tokens](#injection-via-tokens)
    - [Multi injection](#multi-injection)
    - [Inject into React Components](#inject-into-react-components)
    - [Inject into Services](#inject-into-services)
  - [Sharing providers](#sharing-providers)
    - [Importing modules](#importing-modules-in-other-modules)
    - [Hierarchical shared providers](#hierarchical-shared-providers)
  - [Testing](#testing)
  - [API](#api)
    - [Injectable](#injectable)
    - [Inject](#inject)
    - [Module](#module)
    - [providers (Option)](#providers-option)
        - [Injecting a class constructor](#injecting-a-class-constructor)
        - [Injecting a value](#injecting-a-value)
        - [Injection via factories](#injection-via-factories)
    - [imports (Option)](#imports-option)
    - [autoBindInjectable (Option)](#autobindinjectable-option)
    - [TestUtil (React Component)](#testutil-react-component)
    - [Provider (React Component)](#provider-react-component)

- [Development](#development)
<!--te-->

## Overview

Dependency injection for react based upon [inversify](https://github.com/inversify/InversifyJS).

## Install

```
npm install @hooked74/react-di
```

Your `tsconfig.json` needs to be configured with  the following flags:
```
"experimentalDecorators": true,
"emitDecoratorMetadata": true
```

## Usage

## Getting started
#### 1. Define injectables (values/class)
*Service*
```typescript
import {Injectable, Inject} from '@hooked74/react-di';

@Injectable
export class UserService {
  constructor(@Inject private httpClient: SomeHttpClient) {}

  getUser() {}
}
```
*Config*
```typescript
const CONFIG_TOKEN = 'config'; // or Symbol('config');
const config: Config = {...};
```
#### 2. Inject service/value into react component
```typescript jsx
import {Inject} from '@hooked74/react-di';

class UserContainer extends Component<any, any> {
  @Inject userService: UserService;
  @Inject(CONFIG_TOKEN) config: Config;

  async componentDidMount() {
    const user = await this.userService.getUser();
    this.setState({user});
  }

  render() {
    return (<User user={this.state.user} />);
  }
}
```
#### 3. Setup module
```typescript jsx
import {Module} from '@hooked74/react-di';

@Module({
  providers: [
    {provide: UserService, useClass: UserService},
    UserService, // or shorthand
    {provide: CONFIG_TOKEN, useValue: config},
    {provide: CONFIG_TOKEN, useFactory: context => config}, // or using factory
  ]
})
class App extends Component {
  render() {
    return (
        <Panel>
          <UserContainer/>
        </Panel>
    );
  }
}
```

## Injection

### Injection from class references
If you want to inject a service instance, simply mark the property with `@Inject`.
The type from which the instance will be created, will be derived from the
annotated type. Notice, that the type needs to be a class. An interface won't
work, cause it will not exist on runtime anymore (See
[Injection via tokens](#injection-via-tokens) to get it work with interfaces).
```typescript
@Inject userService: UserService;
```
A services class need to be annotated with `@Injectable` otherwise it will not
be - of course - injectable.

### Injection via tokens
Dependencies can also be identified by a token. This makes sense when injecting
a value (for example a configuration) or if you want annotate these properties
with interfaces instead of class references.
```typescript
@Inject(CONFIG_TOKEN) config: Config;
```

### Multi injection
Multi injection means that all providers for a specified identifier
will be injected.
So if the annotated type of the dependency is of type `Array`, the
injection will automatically processed as a multi-injection. In this
case the identifier needs to be passed explicitly:
```typescript
@Inject(INTERCEPTOR_TOKEN) interceptors: Interceptor[];
```

### Inject into React Components
Dependencies of react component need be injected via property injection:
```typescript jsx
import {Inject} from '@hooked74/react-di';

class UserComponent extends Component<any, any> {
  @Inject userService: UserService;
  @Inject(OtherService) otherService: OtherService;
  @Inject(CONFIG_TOKEN) config: Config;
  @Inject(TRANSLATION_TOKEN) translations: Translation[];

  ...
}
```

### Inject into React Functional Components
Dependencies of react component need be injected via `useInject` hook:
```typescript jsx
import {useInject} from '@hooked74/react-di';

const UserComponent = () => {
  const [otherService, config, translation]: [OtherService, Config, Translation] = useInject(OtherService, CONFIG_TOKEN, TRANSLATION_TOKEN)

  ...
}
```

### Inject into Services
Dependencies of services will be injected via constructor injection:
```typescript
import {Injectable, Inject} from '@hooked74/react-di';

@Injectable
export class UserService {
  constructor(@Inject http: HttpClient,
              @Inject(OtherService) otherService: OtherService,
              @Inject(HISTORY_TOKEN) history: IHistory) {}
}
```

## Sharing providers
Providers can be shared via importing from siblings or inheriting them from
parent components.

#### Importing modules in other modules
Modules can be imported in other modules to share its providers.
```typescript jsx
@Module({providers: [CommonService]})
class CommonModule extends Component {}

@Module({
  imports: [CommonModule],
  providers: [UserService],
})
class UserModule extends Component {
  @Inject userService: UserService;
  @Inject commonService: CommonService;
}

@Module()
class Root extends Component {
  render() {
    return (
      <UserModule/>
    );
  }
```

#### Hierarchical shared providers
Nesting `Module` annotated components in another `Module` annotated components
is supported.
All defined providers of the parent module will be inherited to its
child modules:
```typescript jsx
@Module({providers: [UserService]})
class UserModule extends Component {}

@Module({providers: [CommonService]})
class App extends Component {
  render() {
    return (
      <UserModule/> // CommonService will also be available in UserModule
    );
  }
```
Providers that are already defined in a parent, will be overridden
if they are also defined in its child.

## Testing
For testing purposes `@hooked74/react-di` provides a `TestUtil` component. Nest the
components you want to test in `TestUtil` and setup its dependencies in
the `providers` prop of `TestUtil`:
```typescript jsx
import {mount} from 'enzyme';

mount(
  <TestUtil providers={[{provide: UserService, useClass: UserServiceMock}]}>
    <UserProfileContainer/>
  </TestUtil>
);
```
or for defining a much simpler mock:
```typescript jsx
mount(
  <TestUtil providers={[{provide: UserService, useValue: {getUser() {...}}}]}>
    <UserProfileContainer/>
  </TestUtil>
);
```

## API

### Injectable
`Injectable` decorator marks a class as available to the inversify `Container`.
```typescript
import {Injectable} from '@hooked74/react-di';

@Injectable
export class UserService {}
```

### Inject
The `Inject`/`Inject(Identifier)` decorator tells the di-system what need to be injected
and in which property the injected value should be referenced.

For functional components an alternative is the `useInject(...Identifiers)` hook.

### Module
All components that should be feedable with the defined providers,
need to be nested in a module annotated component - But don't(!) need to be
direct children.
```typescript jsx
@Module({
  providers: [...],
  imports: [...],
  autoBindInjectable: true, // default is false
})
class App extends Component {}
```

#### `providers` (Option)
(optional) Array of all available providers.

##### Injecting a class constructor
```typescript
[{provide: UserService, useClass: UserService}]
```
*Shorthand*
```typescript
[UserService]
```
All instantiated dependencies will be a **singleton** by default. If you don't
want a dependency to be singleton set `noSingleton` to `true`:
```typescript
[{provide: UserService, useClass: UserService, noSingleton: true}]
```

##### Injecting a value
```typescript
[{provide: UserService, useValue: someUserService}]
```

##### Injection via factories
Dependencies can be injected via factories. A factory is a simple function,
that gets the context of the current scope and returns the value, that
will be injected.
```typescript
[{provide: UserService, useFactory: context => someValue}]
```

#### `imports` (Option)
(optional) Array of all modules that should be imported.
```typescript
[CommonModule, UserModule]
```

#### `autoBindInjectable` (Option)
(optional, default: `false`) When `autoBindInjectable` is set to `true`, injectable
class constructors don't need to be defined as providers anymore.
They will be available for injection by default.
So that `[{provide: UserService, useClass: UserService}]` or `[UserService]`
can be omitted:
```typescript
@Module({autoBindInjectable: true})
class App extends Component{}
```
```jsx
<TestUtil autoBindInjectable={true}>
  ... // UserService will be available anyway
</TestUtil>
```

#### TestUtil (React Component)
```typescript jsx
<TestUtil providers={[...]} autoBindInjectable={true}>
  ...
</TestUtil>
```

#### Provider (React Component)
The `<Provider>` component is a react component, that provides low-level
support for inversifyJS containers. In other words: It takes an
inversify container as property. So if you want to use all features
of inversify, this is the component you will fall in love with:
```typescript jsx
const container = new Container();
container.bind(Ninja).to(Samurai);

<Provider container={container}>
  ...
</Provider>
```

## Development

You can run some built-in commands:

### **npm run build**

Builds the app for production. Your app is ready to be deployed.

### **npm run analyze**

Visualize and analyze your bundle to see which modules are taking up space.

### **npm run test:watch**

Runs the test watcher in an interactive mode. By default, runs tests related to files changed since the last commit.