react-obsidian 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/testkit/index.d.ts +6 -1
- package/dist/testkit/index.d.ts.map +1 -1
- package/dist/testkit/index.js +11 -17
- package/dist/testkit/index.js.map +1 -1
- package/dist/testkit/mockGraphs.d.ts +4 -0
- package/dist/testkit/mockGraphs.d.ts.map +1 -0
- package/dist/testkit/mockGraphs.js +23 -0
- package/dist/testkit/mockGraphs.js.map +1 -0
- package/dist/testkit/mockModel.d.ts +3 -0
- package/dist/testkit/mockModel.d.ts.map +1 -0
- package/dist/testkit/mockModel.js +14 -0
- package/dist/testkit/mockModel.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/testkit/index.ts +11 -14
- package/testkit/mockGraphs.ts +20 -0
- package/testkit/mockModel.ts +10 -0
- package/.buildkite/pipeline.yml +0 -10
- package/.eslintignore +0 -1
- package/.eslintrc.json +0 -105
- package/.vscode/settings.json +0 -6
- package/babel.config.js +0 -13
- package/documentation/README.md +0 -41
- package/documentation/babel.config.js +0 -3
- package/documentation/blog/2019-05-28-first-blog-post.md +0 -12
- package/documentation/blog/2019-05-29-long-blog-post.md +0 -44
- package/documentation/blog/2021-08-01-mdx-blog-post.mdx +0 -20
- package/documentation/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/documentation/blog/2021-08-26-welcome/index.md +0 -25
- package/documentation/blog/authors.yml +0 -17
- package/documentation/docs/documentation/documentation.mdx +0 -190
- package/documentation/docs/documentation/installation.mdx +0 -56
- package/documentation/docs/documentation/meta/clearingGraphs.mdx +0 -13
- package/documentation/docs/documentation/meta/middlewares.mdx +0 -27
- package/documentation/docs/documentation/usage/ClassComponents.mdx +0 -18
- package/documentation/docs/documentation/usage/Classes.mdx +0 -41
- package/documentation/docs/documentation/usage/FunctionalComponents.mdx +0 -57
- package/documentation/docs/documentation/usage/Graphs.mdx +0 -154
- package/documentation/docs/documentation/usage/Hooks.mdx +0 -85
- package/documentation/docs/documentation/usage/Reactivity.mdx +0 -116
- package/documentation/docs/documentation/usage/ServiceLocator.mdx +0 -38
- package/documentation/docs/documentation/usage/_category_.json +0 -9
- package/documentation/docs/guides/configurableApplications.mdx +0 -205
- package/documentation/docs/guides/mockDependencies.mdx +0 -141
- package/documentation/docusaurus.config.js +0 -151
- package/documentation/package-lock.json +0 -22975
- package/documentation/package.json +0 -47
- package/documentation/sidebars.js +0 -34
- package/documentation/src/components/HomepageFeatures/index.tsx +0 -71
- package/documentation/src/components/HomepageFeatures/styles.module.css +0 -11
- package/documentation/src/css/custom.css +0 -30
- package/documentation/src/pages/index.module.css +0 -23
- package/documentation/src/pages/index.tsx +0 -41
- package/documentation/src/pages/playground/index.mdx +0 -21
- package/documentation/src/theme/SearchBar.js +0 -19
- package/documentation/static/.nojekyll +0 -0
- package/documentation/static/img/api.svg +0 -101
- package/documentation/static/img/favicon.ico +0 -0
- package/documentation/static/img/logo.svg +0 -265
- package/documentation/static/img/obsidian.png +0 -0
- package/documentation/static/img/prototype.svg +0 -1
- package/documentation/static/img/stethoscope.svg +0 -37
- package/documentation/tsconfig.json +0 -7
- package/documentation/yarn.lock +0 -8167
- package/example/.buckconfig +0 -6
- package/example/.editorconfig +0 -3
- package/example/.eslintrc.js +0 -4
- package/example/.flowconfig +0 -65
- package/example/.gitattributes +0 -3
- package/example/.prettierrc.js +0 -7
- package/example/.watchmanconfig +0 -1
- package/example/App.tsx +0 -31
- package/example/MyInjectedComponent.tsx +0 -37
- package/example/__tests__/App-test.js +0 -14
- package/example/android/app/BUCK +0 -55
- package/example/android/app/build.gradle +0 -227
- package/example/android/app/build_defs.bzl +0 -19
- package/example/android/app/debug.keystore +0 -0
- package/example/android/app/proguard-rules.pro +0 -10
- package/example/android/app/src/debug/AndroidManifest.xml +0 -13
- package/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java +0 -72
- package/example/android/app/src/main/AndroidManifest.xml +0 -25
- package/example/android/app/src/main/java/com/example/MainActivity.java +0 -15
- package/example/android/app/src/main/java/com/example/MainApplication.java +0 -80
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/example/android/app/src/main/res/values/strings.xml +0 -3
- package/example/android/app/src/main/res/values/styles.xml +0 -8
- package/example/android/build.gradle +0 -38
- package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -5
- package/example/android/gradle.properties +0 -28
- package/example/android/gradlew +0 -185
- package/example/android/gradlew.bat +0 -89
- package/example/android/settings.gradle +0 -3
- package/example/app.json +0 -4
- package/example/babel.config.js +0 -8
- package/example/index.js +0 -9
- package/example/ios/Podfile +0 -30
- package/example/ios/Podfile.lock +0 -526
- package/example/ios/example/AppDelegate.h +0 -8
- package/example/ios/example/AppDelegate.m +0 -62
- package/example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json +0 -38
- package/example/ios/example/Images.xcassets/Contents.json +0 -6
- package/example/ios/example/Info.plist +0 -55
- package/example/ios/example/LaunchScreen.storyboard +0 -47
- package/example/ios/example/main.m +0 -9
- package/example/ios/example.xcodeproj/project.pbxproj +0 -689
- package/example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme +0 -88
- package/example/ios/example.xcworkspace/contents.xcworkspacedata +0 -10
- package/example/ios/example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/exampleTests/Info.plist +0 -24
- package/example/ios/exampleTests/exampleTests.m +0 -65
- package/example/metro.config.js +0 -44
- package/example/package.json +0 -35
- package/example/tsconfig.json +0 -42
- package/global.d.ts +0 -1
- package/jest.config.js +0 -17
- package/jest.setup-after-env.js +0 -4
- package/tsconfig.base.json +0 -42
- package/tsconfig.json +0 -3
- package/tsconfig.prod.json +0 -7
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 1
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
import Tabs from '@theme/Tabs';
|
|
6
|
-
import TabItem from '@theme/TabItem';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Introduction
|
|
10
|
-
|
|
11
|
-
Obsidian is a dependency injection container with first-class support for React and React Native applications.
|
|
12
|
-
|
|
13
|
-
Get started by **following the installation guide** bellow. Or **try Obsidian immediately** in the **[Online Playground](/playground)**.
|
|
14
|
-
|
|
15
|
-
## The 2 steps tutorial for injecting dependencies with Obsidian
|
|
16
|
-
|
|
17
|
-
### Step 1: Declare how dependencies should be created
|
|
18
|
-
|
|
19
|
-
Define a singleton graph that is instantiated once and is retained throughout the lifespan of the application. All dependencies it provides are also singletons. The graph bellow provides two dependencies that can be injected: `fooService` and `barManager`.
|
|
20
|
-
```ts title="A singleton graph that provides two dependencies"
|
|
21
|
-
import {Singleton, Graph, ObjectGraph, Provides} from 'react-obsidian';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@Singleton() @Graph()
|
|
25
|
-
export class ApplicationGraph extends ObjectGraph {
|
|
26
|
-
|
|
27
|
-
// fooService requires a barManager so it receives one as a parameter.
|
|
28
|
-
@Provides()
|
|
29
|
-
fooService(barManager: BarManager): FooService {
|
|
30
|
-
return new FooService(barManager);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@Provides()
|
|
35
|
-
barManager(): BarManager {
|
|
36
|
-
return new BarManager();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Step 2: Inject the dependencies
|
|
42
|
-
Obsidian can inject dependencies into components, hooks, and classes.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<Tabs>
|
|
47
|
-
<TabItem value="functionalComponent" label="Functional component injection" default>
|
|
48
|
-
|
|
49
|
-
Injecting React functional components essentially revolves around two things: declaring the required dependencies in the components's props and exporting an injected component using the `injectComponent` function.
|
|
50
|
-
|
|
51
|
-
```ts title="MyComponent.tsx"
|
|
52
|
-
import {DependenciesOf, injectComponent} from 'react-obsidian';
|
|
53
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
54
|
-
|
|
55
|
-
// 1. Declare which dependencies should be injected.
|
|
56
|
-
type Props = DependenciesOf<ApplicationGraph, 'fooService'>; // {fooService: FooService}
|
|
57
|
-
|
|
58
|
-
// 2. Implement the component.
|
|
59
|
-
const myComponent = ({fooService}: Props) => {
|
|
60
|
-
// Do something useful with fooService
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 3. Export the injected component.
|
|
64
|
-
export default injectComponent(myComponent, ApplicationGraph);
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Now we can use the injected component without providing its dependencies manually:
|
|
68
|
-
```tsx title="SomeComponent.tsx"
|
|
69
|
-
import MyComponent from './MyComponent';
|
|
70
|
-
|
|
71
|
-
const SomeComponent = () => {
|
|
72
|
-
// 4. Render the component - its dependencies are resolved automatically by Obsidian.
|
|
73
|
-
return <MyComponent />;
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
</TabItem>
|
|
77
|
-
<TabItem value="hook" label="Hook injection">
|
|
78
|
-
|
|
79
|
-
Hooks are injected in a similar way to functional components. The only difference is that the `injectHook` function is used instead of `injectComponent`.
|
|
80
|
-
|
|
81
|
-
```ts title="MyHook.ts"
|
|
82
|
-
import {DependenciesOf, injectHook} from 'react-obsidian';
|
|
83
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
84
|
-
|
|
85
|
-
// 1. Declare which dependencies should be injected.
|
|
86
|
-
type Props = DependenciesOf<ApplicationGraph, 'fooService'>; // {fooService: FooService}
|
|
87
|
-
|
|
88
|
-
// 2. Implement the hook.
|
|
89
|
-
const myHook = ({fooService}: Props) => {
|
|
90
|
-
// Do something useful with fooService
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 3. Export the injected hook.
|
|
94
|
-
export default injectHook(myHook, ApplicationGraph);
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The injected hook can be used without providing its dependencies manually:
|
|
98
|
-
```tsx title="SomeComponent.tsx"
|
|
99
|
-
import myHook from './MyHook';
|
|
100
|
-
|
|
101
|
-
const SomeComponent = () => {
|
|
102
|
-
// 4. Use the hook without providing any dependencies manually - they are injected automatically.
|
|
103
|
-
myHook();
|
|
104
|
-
|
|
105
|
-
return <>Obsidian is awesome!</>;
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
</TabItem>
|
|
109
|
-
<TabItem value="classComponent" label="Class component injection">
|
|
110
|
-
|
|
111
|
-
To inject a class, annotate it with the `@Injectable` decorator. The `@Injectable` decorator takes a single parameter - the graph that should be used to resolve the dependencies. Declare the dependencies as class members and annotate them with the `@Inject` decorator.
|
|
112
|
-
|
|
113
|
-
```ts title="MyComponent.tsx"
|
|
114
|
-
import {Injectable, Inject} from 'react-obsidian';
|
|
115
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
116
|
-
|
|
117
|
-
@Injectable(ApplicationGraph)
|
|
118
|
-
export MyClassComponent extends React.Component {
|
|
119
|
-
@Inject() private fooService!: FooService;
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Render the injected component. Obsidian resolves the required dependencies automatically.
|
|
125
|
-
```tsx title="SomeComponent.tsx"
|
|
126
|
-
import MyComponent from './MyComponent';
|
|
127
|
-
|
|
128
|
-
const SomeComponent = () => {
|
|
129
|
-
// 4. Render the component - its dependencies are resolved automatically by Obsidian.
|
|
130
|
-
return <MyComponent />;
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
</TabItem>
|
|
135
|
-
<TabItem value="class" label="Class constructor injection">
|
|
136
|
-
|
|
137
|
-
To inject a class, annotate it with the `@Injectable` decorator. The `@Injectable` decorator takes a single parameter - the graph that should be used to resolve the dependencies.
|
|
138
|
-
Declare the dependencies as constructor parameters and annotate them with the `@Inject` decorator.
|
|
139
|
-
|
|
140
|
-
```ts title="MyClass.tsx"
|
|
141
|
-
import {Injectable, Inject} from 'react-obsidian';
|
|
142
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
143
|
-
|
|
144
|
-
@Injectable(ApplicationGraph)
|
|
145
|
-
export MyClass {
|
|
146
|
-
constructor (fooService?: FooService);
|
|
147
|
-
constructor(@Inject() private fooService: FooService) { }
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Now we can use the injected class without providing its dependencies manually:
|
|
152
|
-
```ts
|
|
153
|
-
const myClass = new MyClass();
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Of course, passing dependencies explicitly is still possible:
|
|
157
|
-
```ts
|
|
158
|
-
const myClass = new MyClass(new FooService());
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
</TabItem>
|
|
162
|
-
</Tabs>
|
|
163
|
-
|
|
164
|
-
___
|
|
165
|
-
|
|
166
|
-
## Features
|
|
167
|
-
|
|
168
|
-
* ⚛️ Inject all React constructs
|
|
169
|
-
* Functional components
|
|
170
|
-
* Hooks
|
|
171
|
-
* Class components
|
|
172
|
-
* 🛠 Improve code structure
|
|
173
|
-
* Easily write object-oriented code with Single Responsibility in mind
|
|
174
|
-
* Eliminate circular dependencies
|
|
175
|
-
* Avoid implicit dependencies to make your code easier to reason about
|
|
176
|
-
* ❤️ Developer experience
|
|
177
|
-
* Seamlessly integrates into existing projects
|
|
178
|
-
* Easy to adopt gradually
|
|
179
|
-
* Scales well
|
|
180
|
-
* Idiomatic API that's easy to understand
|
|
181
|
-
|
|
182
|
-
## Design principles
|
|
183
|
-
|
|
184
|
-
React Obsidian is guided by the principles of the Dependency Injection pattern, but does not strictly follow them. We allowed ourselves a degree of freedom when designing the library in order to reduce boilerplate code and library footprint.
|
|
185
|
-
|
|
186
|
-
* **Easy to start** - Obsidian requires very little code to get you started. Once you declare a graph, using it to inject dependencies requires as little as two lines of code.
|
|
187
|
-
* **Intuitive API** - The API should be verbose and understandable even to new users without prior experience with Dependency Injection.
|
|
188
|
-
* **Minimal boilerplate** - Require the bare minimum in order to construct dependencies and resolve them.
|
|
189
|
-
|
|
190
|
-
<!-- ## Comparison with other libraries -->
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 2
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Installation
|
|
6
|
-
|
|
7
|
-
Like most Dependency Injection frameworks, Obsidian uses automatic code generation to create the bindings necessary for resolving dependencies. This approach helps reduce the amount of boilerplate code required by developers. Obsidian relies on Babel for code generation, so you'll need to have Babel configured in your project.
|
|
8
|
-
|
|
9
|
-
## 1. Install Obsidian
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install react-obsidian
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## 2. Install Reflect-metadata
|
|
16
|
-
First, install and enable the reflect-metadata polyfill.
|
|
17
|
-
```bash
|
|
18
|
-
npm install reflect-metadata
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Then, add the following line to the top of your application's entry point (usually index.js or index.ts):
|
|
22
|
-
```js
|
|
23
|
-
import 'reflect-metadata';
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## 3. Enable experimental decorators
|
|
27
|
-
Obsidian uses the Decorators feature whose proposal is at stage 3.
|
|
28
|
-
|
|
29
|
-
Add the following options to your tsconfig.json file.
|
|
30
|
-
|
|
31
|
-
```js
|
|
32
|
-
{
|
|
33
|
-
"compilerOptions": {
|
|
34
|
-
"experimentalDecorators": true,
|
|
35
|
-
"emitDecoratorMetadata": true
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## 4. Add the required Babel plugins
|
|
41
|
-
Add the transformer to the list of plugins in your `.babel` file.
|
|
42
|
-
|
|
43
|
-
```diff
|
|
44
|
-
module.exports = {
|
|
45
|
-
presets: [
|
|
46
|
-
'module:metro-react-native-babel-preset',
|
|
47
|
-
+ ['@babel/preset-typescript', {'onlyRemoveTypeImports': true}]
|
|
48
|
-
],
|
|
49
|
-
plugins: [
|
|
50
|
-
+ react-obsidian/dist/transformers/babel-plugin-obsidian,
|
|
51
|
-
+ ['@babel/plugin-proposal-decorators', {legacy: true}],
|
|
52
|
-
+ '@babel/plugin-transform-class-properties',
|
|
53
|
-
+ 'babel-plugin-parameter-decorator'
|
|
54
|
-
]
|
|
55
|
-
};
|
|
56
|
-
```
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 2
|
|
3
|
-
title: "Clearing graphs"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Graphs can be cleared by invoking the `Obsidian.clearGraphs()` function. This is useful in tests or when you need to reset the system to it's original state, for example after a user logs out.
|
|
7
|
-
|
|
8
|
-
#### Clearing graphs automatically during execution of Jest tests
|
|
9
|
-
Create a `jest.setup.js` file and add it to [setupFilesAfterEnv](https://jestjs.io/docs/configuration#setupfilesafterenv-array). Then, import the following file when ensures graphs are cleared before each test.
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import 'react-obsidian/clearGraphs';
|
|
13
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 1
|
|
3
|
-
title: "Graph middlewares"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Graph middlewares let you plug into the graph creation process and modify the graph in any way you want. This is useful when working on large scale applications where "observability" is a key concern. For example, you can use a middleware to swizzle providers, add logging, or even add a new provider to the graph.
|
|
7
|
-
|
|
8
|
-
Middleware follow the Chain of Responsibility pattern and therefore must always return a graph, either by creating one explicitly or by returning the instance created by another member in the resolve chain.
|
|
9
|
-
|
|
10
|
-
## Example: adding a logging middleware
|
|
11
|
-
The following example demonstrates how to add a middleware that's used for logging purposes.
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { GraphMiddleware } from 'react-obsidian';
|
|
15
|
-
|
|
16
|
-
const loggingMiddleware = new class extends GraphMiddleware {
|
|
17
|
-
resolve<Props>(resolveChain: GraphResolveChain, Graph: Constructable<T>, props?: Props) {
|
|
18
|
-
const t1 = Date.now();
|
|
19
|
-
const graph = resolveChain.proceed(Graph, props);
|
|
20
|
-
const t2 = Date.now();
|
|
21
|
-
console.log(`Graph created in ${t2 - t1} milliseconds`);
|
|
22
|
-
return graph;
|
|
23
|
-
}
|
|
24
|
-
}();
|
|
25
|
-
|
|
26
|
-
Obsidian.addGraphMiddleware(loggingMiddleware);
|
|
27
|
-
```
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 4
|
|
3
|
-
title: "Class components"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Injecting class components
|
|
7
|
-
Injecting class components is a two step process. First, annotate the class with the `@Injectable` annotation and pass the graph from which dependencies should be resolve. Then, declare the dependencies as class members and annotate them with the `@Inject` annotation.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import {Injectable, Inject} from 'react-obsidian';
|
|
11
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
12
|
-
|
|
13
|
-
@Injectable(ApplicationGraph)
|
|
14
|
-
export class ClassComponent extends React.Component {
|
|
15
|
-
@Inject() private httpClient!: HttpClient;
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
```
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 5
|
|
3
|
-
title: "Classes"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Injecting classes
|
|
7
|
-
Injecting classes is a two step process. First, annotate the class with the `@Injectable` annotation and pass the graph from which dependencies should be resolve. Then, declare the dependencies as class members and annotate them with the `@Inject` annotation.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import {Injectable, Inject} from 'react-obsidian';
|
|
11
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
12
|
-
|
|
13
|
-
@Injectable(ApplicationGraph)
|
|
14
|
-
export class MyClass {
|
|
15
|
-
@Inject() private httpClient!: HttpClient;
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
:::important Always prefer constructor injection over field injection
|
|
21
|
-
Constructor injection is the preferred way to inject dependencies. It is more explicit and easier to test. **Field injection should only be used when a class is not instantiated by a graph.**
|
|
22
|
-
:::
|
|
23
|
-
|
|
24
|
-
## Delayed injection
|
|
25
|
-
Dependencies annotated with the `@Inject` annotation are resolved immediately **after** the constructor is called. If you want to inject a class at a later point in time, you can use the `@LateInject` annotation instead, and inject the dependencies by manually with the `Obsidian.inject()` function.
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import {Injectable, LateInject} from 'react-obsidian';
|
|
29
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
30
|
-
|
|
31
|
-
@Injectable(ApplicationGraph)
|
|
32
|
-
export class MyClass {
|
|
33
|
-
@LateInject() private httpClient!: HttpClient;
|
|
34
|
-
|
|
35
|
-
public init() {
|
|
36
|
-
console.log(this.httpClient === undefined); // true
|
|
37
|
-
Obsidian.inject(this);
|
|
38
|
-
console.log(this.httpClient === undefined); // false
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 3
|
|
3
|
-
title: "Functional components"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Injecting functional components
|
|
7
|
-
Component injection is identical in principle to how hooks are injected. The only difference is that instead of using the `injectHook` function, you use the `injectComponent` function. The `injectComponent` function takes the same arguments as the `injectHook` function, except that it takes a component as the second argument instead of a hook.
|
|
8
|
-
|
|
9
|
-
```tsx title="Injecting a functional component"
|
|
10
|
-
import { injectComponent, DependenciesOf } from 'react-obsidian';
|
|
11
|
-
import { ApplicationGraph } from './ApplicationGraph';
|
|
12
|
-
|
|
13
|
-
const MyComponent = ({httpService}: DependenciesOf<ApplicationGraph, 'httpClient'>) => {
|
|
14
|
-
return <div>My component</div>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default injectComponent(MyComponent, ApplicationGraph);
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
:::tip Prefer injecting hooks over components
|
|
21
|
-
Every entity (class, functional component etc.) in your application should have a single responsibility, and as such, there should be only one reason to change it. Components are responsible for rendering the UI, and therefor should change only when UI requirements change.
|
|
22
|
-
|
|
23
|
-
Prefer injecting hooks that will bridge between components and application logic. This allows components to emphasize "what" they need instead of "how", thus, preventing implementation details from leaking into them.
|
|
24
|
-
:::
|
|
25
|
-
|
|
26
|
-
## Strongly typed components
|
|
27
|
-
The `injectComponent` function leverages *generics* to correctly type injected components.
|
|
28
|
-
|
|
29
|
-
### Typing components that require props and injected dependencies
|
|
30
|
-
In cases where a component requires both props and injected dependencies, we recommend typing them separately and declaring the component's props as the intersection of the two types. This way the component returned by the `injectComponent` function will require its `Own` props while all `Injected` dependencies will be marked as optional. `Injected` dependencies are marked as optional because they can either be injected manually or automatically by Obsidian.
|
|
31
|
-
|
|
32
|
-
```tsx title="Separate declaration for passed (own) props and injected dependencies"
|
|
33
|
-
import { injectComponent, DependenciesOf } from 'react-obsidian';
|
|
34
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
35
|
-
|
|
36
|
-
type Injected = DependenciesOf<ApplicationGraph, 'httpClient'>;
|
|
37
|
-
type Own = {name: string};
|
|
38
|
-
|
|
39
|
-
const MyComponent = ({name, httpService}: Own & Injected) => {
|
|
40
|
-
return <div>Hey, my name is: {name} 👋</div>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// The result type is React.FunctionComponent<{name: string , httpClient?: HttpClient}>
|
|
44
|
-
export default injectComponent<Own, Injected>(MyComponent, ApplicationGraph);
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Typing components that don't require Props
|
|
48
|
-
If a component doesn't require any props from its parent component, simply use the `DependenciesOf` utility type provided by Obsidian to type the component's props. There's no need to use generics in this case as all props will are marked as optional.
|
|
49
|
-
|
|
50
|
-
```tsx title="Typing components that don't require props"
|
|
51
|
-
const MyComponent = ({httpService}: DependenciesOf<ApplicationGraph, 'httpClient'>) => {
|
|
52
|
-
return <div>Hello world 👋</div>
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// The result type is React.FunctionComponent<{httpClient?: HttpClient}>
|
|
56
|
-
export default injectComponent(MyComponent, ApplicationGraph);
|
|
57
|
-
```
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 1
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
## Introduction
|
|
6
|
-
|
|
7
|
-
In Object Oriented Programming, programs are organized around objects, where each object has a specific purpose. These objects can require other objects to perform their responsibilities. The required objects are called dependencies. Providing these dependencies manually is a tedious and error-prone process. The dependency injection pattern is a way to automate this process so you can focus on the logic of your application instead of writing boilerplate code.
|
|
8
|
-
|
|
9
|
-
Before you can inject dependencies into hooks, components and classes, the dependencies first need to be declared so Obsidian knows how to construct them. In Obsidian, dependencies are declared in classes called "Graphs". Graphs create a centralized place where dependencies are defined. This makes them a powerful tool for understanding the relationships between objects in your program.
|
|
10
|
-
|
|
11
|
-
## Declaring dependencies in a graph
|
|
12
|
-
The snippet below shows a basic example of a Graph. It defines two dependencies, `httpClient` and `databaseService`.
|
|
13
|
-
|
|
14
|
-
```ts title="ApplicationGraph.ts"
|
|
15
|
-
import {Singleton, Graph, ObjectGraph, Provides} from 'react-obsidian';
|
|
16
|
-
|
|
17
|
-
@Singleton() @Graph()
|
|
18
|
-
export class ApplicationGraph extends ObjectGraph {
|
|
19
|
-
@Provides()
|
|
20
|
-
httpClient(): HttpClient {
|
|
21
|
-
return new HttpClient();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@Provides()
|
|
25
|
-
databaseService(): DatabaseService {
|
|
26
|
-
return new DatabaseService();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Graphs must be annotated with the `@Graph` decorator. In this example we chose to annotate the class with the `@Singleton` decorator as well, which means that the graph and the dependencies it provides will only be constructed once.
|
|
32
|
-
|
|
33
|
-
Dependencies are constructed in methods annotated with the `@Provides` annotation. The `@Provides` annotation is used to tell Obsidian that the method is a dependency provider. From now on we'll refer to these methods as providers. Obsidian uses the provider's method name as the dependency's name. In this example, the `httpClient` provider method provides the `httpClient` dependency. The `databaseService` provider method provides the `databaseService` dependency.
|
|
34
|
-
|
|
35
|
-
Once your graph is declared you can use it to inject dependencies into the various constructs that form your application:
|
|
36
|
-
* [Inject hooks](/docs/documentation/usage/hooks#injecting-hooks)
|
|
37
|
-
* [Inject functional components](/docs/documentation/usage/FunctionalComponents)
|
|
38
|
-
* [Inject components](/docs/documentation/usage/ClassComponents)
|
|
39
|
-
* [Inject classes](/docs/documentation/usage/Classes)
|
|
40
|
-
|
|
41
|
-
:::info Did you know?
|
|
42
|
-
The term "graph" comes from [graph theory](https://en.wikipedia.org/wiki/Graph_theory). Obsidian constructs [Directed Acyclic Graphs](https://en.wikipedia.org/wiki/Directed_acyclic_graph) (DAGs) to represent the dependencies between objects. This type of graph ensures there are no circular dependencies between objects which cause call stack overflows and other unexpected bugs.
|
|
43
|
-
:::
|
|
44
|
-
|
|
45
|
-
## Specifying relationships between dependencies
|
|
46
|
-
Some of the services defined in your graphs may be independent, meaning they don't require any dependencies to be constructed. However, most of the time, services will require other services to perform their responsibilities. In these cases, you can specify the dependencies of a service as arguments in the provider and Obsidian will resolve them automatically.
|
|
47
|
-
|
|
48
|
-
```ts title="A graph that provides a service that depends on other services"
|
|
49
|
-
import {Singleton, Graph, ObjectGraph, Provides} from 'react-obsidian';
|
|
50
|
-
|
|
51
|
-
@Singleton() @Graph()
|
|
52
|
-
export class ApplicationGraph extends ObjectGraph {
|
|
53
|
-
@Provides()
|
|
54
|
-
httpClient(): HttpClient {
|
|
55
|
-
return new HttpClient();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@Provides()
|
|
59
|
-
databaseService(): DatabaseService {
|
|
60
|
-
return new DatabaseService();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@Provides()
|
|
64
|
-
appInitializer(httpClient: HttpClient, databaseService: DatabaseService): AppInitializer {
|
|
65
|
-
return new AppInitializer(httpClient, databaseService);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
:::info
|
|
71
|
-
Providers are evaluated lazily. This means that a provider is evaluated only when the dependency it provides is requested. Dependencies that are not used in the application, will never be constructed.
|
|
72
|
-
:::
|
|
73
|
-
|
|
74
|
-
## Graph types
|
|
75
|
-
|
|
76
|
-
There are two types of graphs in Obsidian: A singleton graph and a lifecycle-bound graph.
|
|
77
|
-
|
|
78
|
-
### The singleton graph
|
|
79
|
-
Applications typically have at least one singleton graph. These graphs are used to provide dependencies that are used throughout the application. These dependencies are usually singletons, which means they should only be constructed once. The `ApplicationGraph` in the [example above](/docs/documentation/usage/Graphs#specifying-relationships-between-dependencies) is a singleton graph.
|
|
80
|
-
|
|
81
|
-
To declare a singleton graph, annotate the graph class with the `@Singleton` decorator.
|
|
82
|
-
|
|
83
|
-
### The lifecycle-bound graph
|
|
84
|
-
Lifecycle-bound graphs are used to provide dependencies that are shared between components and hooks in a specific UI flow.
|
|
85
|
-
|
|
86
|
-
Dependencies provided by a lifecycle-bound graph are treated as singletons within the scope of the components or hooks that depend on that graph. When a component or hook that depends on a lifecycle-bound graph is mounted, Obsidian will reuse an existing instance of the graph if one exists. If no instance of the graph exists, Obsidian will construct a new instance of the graph. Once all components or hooks that use the graph are unmounted, the graph is destroyed.
|
|
87
|
-
|
|
88
|
-
In other words, dependencies provided by lifecycle-bound graphs are:
|
|
89
|
-
1. Constructed and destroyed when the flow starts and ends.
|
|
90
|
-
2. Shared between all components and hooks that take part in the flow.
|
|
91
|
-
|
|
92
|
-
#### Passing props to a lifecycle-bound graph
|
|
93
|
-
When a graph is created, it receives the props of the component or hook that requested it. This means that the graph can use the props to construct the dependencies it provides. The `@LifecycleBound` in the example below graph provides a `userService` which requires a `userId`. The `userId` is obtained from props.
|
|
94
|
-
|
|
95
|
-
```ts title="A lifecycle-bound graph"
|
|
96
|
-
import {LifecycleBound, Graph, ObjectGraph, Provides} from 'react-obsidian';
|
|
97
|
-
|
|
98
|
-
type HomeScreenProps {
|
|
99
|
-
userId: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@LifecycleBound() @Graph()
|
|
103
|
-
class HomeGraph extends ObjectGraph<HomeScreenProps> {
|
|
104
|
-
private userId: string;
|
|
105
|
-
|
|
106
|
-
construct(props: HomeScreenProps) {
|
|
107
|
-
super(props);
|
|
108
|
-
this.userId = props.userId;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
@Provides()
|
|
112
|
-
userService(): UserService {
|
|
113
|
-
return new UserService(this.userId);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Graph composition
|
|
119
|
-
Graph composition is a powerful feature that allows you to create complex dependency graphs by combining smaller graphs. Composing graphs is useful when you want to reuse a graph in multiple places. For example, you might have a singleton graph that provides application-level dependencies. You might also have a lifecycle-bound graph that provides dependencies for a specific UI flow. You can compose these graphs together so that the lifecycle-bound graph can also inject the dependencies provided by the singleton graph.
|
|
120
|
-
|
|
121
|
-
To compose graphs, pass a `subgraphs` array to the `@Graph` decorator. The `subgraphs` array contains the graphs you want to "include" in your graph.
|
|
122
|
-
|
|
123
|
-
In the example below we declared a lifecycle-bound graph called `LoginGraph`. This graph provides a single dependency called `loginService` which has a dependency on `httpClient`. Since `httpClient` is exposed via the `ApplicationGraph`, we included it in the `subgraphs` array of our graph.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
```ts title="LoginGraph.ts"
|
|
127
|
-
import {Graph, ObjectGraph, Provides} from 'react-obsidian';
|
|
128
|
-
import {ApplicationGraph} from './ApplicationGraph';
|
|
129
|
-
|
|
130
|
-
@LifecycleBound() @Graph({subgraphs: [ApplicationGraph]})
|
|
131
|
-
export class LoginGraph extends ObjectGraph {
|
|
132
|
-
@Provides()
|
|
133
|
-
loginService(httpClient: HttpClient): LoginService {
|
|
134
|
-
return new LoginService(httpClient);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Typed dependencies
|
|
140
|
-
The `DependenciesOf` utility type creates a new type consisting the dependencies provided by a graph. This type can be used to type the dependencies of hooks or props required by components. This utility type takes two arguments: the graph and a union of the keys of the dependencies we want to inject.
|
|
141
|
-
|
|
142
|
-
In this example we create a type called `ApplicationDependencies` which contains the dependencies `httpClient` and `databaseService` from the `ApplicationGraph` graph.
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
// {httpClient: HttpClient, databaseService: DatabaseService}
|
|
146
|
-
type Dependencies = DependenciesOf<ApplicationGraph, 'httpClient' | 'databaseService'>;
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
In cases where a graph has subgraphs, we can pass an array of graphs to the `DependenciesOf` utility type to create a type that contains the dependencies from all the graphs. Using the `LoginGraph` from the example above, we create a type that contains dependencies from both the `LoginGraph` and the `ApplicationGraph`:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
// {httpClient: HttpClient, loginService: LoginService}
|
|
153
|
-
type Dependencies = DependenciesOf<[LoginGraph, ApplicationGraph], 'httpClient' | 'loginService'>;
|
|
154
|
-
```
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
sidebar_position: 2
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
## Injecting hooks
|
|
6
|
-
|
|
7
|
-
Hooks are a fundamental construct in React. They allow you to plug into the lifecycle of a component or react to user interaction and execute code accordingly. As such, hooks typically need to interact with services where the actual logic is implemented.
|
|
8
|
-
|
|
9
|
-
Injecting hooks is simple and straightforward. Simply wrap your hook with the `injectHook` function and declare any required dependencies as destructured properties.
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import { injectHook, DependenciesOf } from 'react-obsidian';
|
|
13
|
-
import { ApplicationGraph } from './ApplicationGraph';
|
|
14
|
-
|
|
15
|
-
const myHook = ({fooService, barService}: Props) => {
|
|
16
|
-
// do something with fooService and barService
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type Props = DependenciesOf<ApplicationGraph, 'fooService' | 'barService'>; // {fooService: FooService, barService: BarService}
|
|
20
|
-
|
|
21
|
-
export injectHook(myHook, ApplicationGraph);
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Strongly typed hooks
|
|
25
|
-
Writing strongly typed hooks is important to ensure that your hooks are easy to use and that you don't accidentally break them when refactoring.
|
|
26
|
-
|
|
27
|
-
### Combining injected dependencies and required arguments
|
|
28
|
-
Sometimes you want to inject hooks with dependencies, but also require additional arguments that will be passed from the calling scope. This is possible by using the `injectHookWithArguments` function.
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { injectHook, DependenciesOf } from 'react-obsidian';
|
|
32
|
-
import { ApplicationGraph } from './ApplicationGraph';
|
|
33
|
-
|
|
34
|
-
type Injected = DependenciesOf<ApplicationGraph, 'fooService'>; // {fooService: FooService}
|
|
35
|
-
type Own = {count: number};
|
|
36
|
-
|
|
37
|
-
const myHook = ({fooService, count}: Injected & Own) => {
|
|
38
|
-
// do something with fooService and count
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default injectHookWithArguments<Injected, Own>(myHook, ApplicationGraph);
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
When using `myHook`, we must pass the `count` argument otherwise we will get a type error. `fooService`, on the other hand, is optional and will be injected from the graph unless passed explicitly.
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
import myHook from './myHook';
|
|
48
|
-
|
|
49
|
-
const MyComponent = () => {
|
|
50
|
-
const [count, setCount] = useState(1337);
|
|
51
|
-
myHook({count});
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Typing the return value of a hook
|
|
57
|
-
When using the standard `injectHook` function, the return value of the hook is inferred automatically by TypeScript. However, when using `injectHookWithArguments`, due to limitations of TypeScript's Generics system, the return value is not inferred and you'll have to specify it explicity.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import { injectHook, DependenciesOf } from 'react-obsidian';
|
|
61
|
-
import { ApplicationGraph } from './ApplicationGraph';
|
|
62
|
-
|
|
63
|
-
type Injected = DependenciesOf<ApplicationGraph, 'fooService'>;
|
|
64
|
-
type Own = {count: number};
|
|
65
|
-
type Result: {onPress: () => void};
|
|
66
|
-
|
|
67
|
-
const myHook = ({fooService, count}: Injected & Own): Result => {
|
|
68
|
-
const onPress = useCallback(() => {
|
|
69
|
-
fooService.doSomething(count);
|
|
70
|
-
}, [fooService, count]);
|
|
71
|
-
return {onPress};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default injectHookWithArguments<Injected, Own, Result>(myHook, ApplicationGraph);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Now the onPress callback is typed correctly when using the injected hook.
|
|
78
|
-
```tsx
|
|
79
|
-
import myHook from './myHook';
|
|
80
|
-
|
|
81
|
-
const MyComponent = () => {
|
|
82
|
-
const [count, setCount] = useState(1337);
|
|
83
|
-
const {onPress} = myHook({count});
|
|
84
|
-
}
|
|
85
|
-
```
|