You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: README.md
+83-72
Original file line number
Diff line number
Diff line change
@@ -28,7 +28,7 @@ Several dependency injection solutions exist for TypeScript. Most use either dec
28
28
29
29
<spanstyle="color:green">✓</span> Achieving loose coupling in large applications
30
30
31
-
tsinject works by defining named factory functions in a container builder, with unique symbols mapping services available in the container to their type. These factory functions can return anything, allowing configuration objects, class instances, functions or any other type of value to be defined as a container service. Any code can be containerized without need for modifications such as annotations or static properties.
31
+
**tsinject** works by defining named factory functions in a container builder, with unique symbols mapping services available in the container to their type. These factory functions can return anything, allowing configuration objects, class instances, functions or any other type of value to be defined as a container service. Any code can be containerized without need for modifications such as annotations or static properties.
32
32
33
33
Any application that does something useful needs to cause side effects. These might include:
34
34
@@ -37,7 +37,7 @@ Any application that does something useful needs to cause side effects. These mi
37
37
- Asking the user for input
38
38
- Logging a message to the console
39
39
40
-
These capabilities are implemented by components of the application, with some components depending on others, and with the implementation or configuration of components often depending on values read from the environment. The quickest way to allow components to communicate with each other is often via globally defined singleton instances. This increases complexity, making code more difficult to debug, test and maintain. Instead, by building components that have their dependencies injected, we can create complex but decoupled applications.
40
+
These capabilities are implemented by components of the application, with some components depending on others, and with the implementation or configuration of components often depending on values read from the environment. The quickest way to allow components to communicate with each other is often via globally defined singleton instances. Importing these global side effects throughout an application can increase complexity, making code more difficult to debug, test and maintain. Instead, by building components that have their dependencies injected, we can create complex but decoupled applications.
41
41
42
42
## Usage
43
43
@@ -84,35 +84,39 @@ import simpleLogFormatter from '../../logging/simpleLogFormatter'
84
84
const loggingModule:ContainerModule<
85
85
processEnvModule.services&
86
86
LoggingServices
87
-
> =builder=>builder
88
-
// Use another container module that provides services required by this one
89
-
.use(processEnvModule.default)
90
-
// Define a config object based on environment variables
91
-
.define(
92
-
keys.loggerConfig,
93
-
container=>loggerConfigFromEnv(
94
-
container.get(processEnvModule.keys.processEnv)
87
+
> = {
88
+
// Specify a unique key for the module
89
+
key: Symbol('loggingModule'),
90
+
build: builder=>builder
91
+
// Use another container module that provides services required by this one
92
+
.use(processEnvModule.default)
93
+
// Define a config object based on environment variables
94
+
.define(
95
+
keys.loggerConfig,
96
+
container=>loggerConfigFromEnv(
97
+
container.get(processEnvModule.keys.processEnv)
98
+
)
95
99
)
96
-
)
97
-
// Provide a different implementation depending on environment variable configuration
**Note:** We should only call [IContainer.get](https://mgdigital.github.io/tsinject/interfaces/IContainer.html#get) from within a factory function or from the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/), avoiding the [service locator anti-pattern](https://freecontent.manning.com/the-service-locator-anti-pattern/).
136
-
137
-
**Note:** When defining a service in the container, if that service key is already defined then the key will **not** be overwritten. This allows modules to be used multiple times without introducing unpredictable behaviour when using decorators. For example, if modules A and B both depend on module C, they can both use module C, and then be used together module D, where they will share the same dependency instance from module C. If an already defined service needs to be overwritten, this can be done with a decorator.
139
+
**Note:** We should only call [Container.get](https://mgdigital.github.io/tsinject/interfaces/Container.html#get) from within a factory function or from the [composition root](https://freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/), avoiding the [service locator anti-pattern](https://freecontent.manning.com/the-service-locator-anti-pattern/).
138
140
139
141
### Decorators
140
142
@@ -145,32 +147,35 @@ Decorators allow us to modify an already-defined service. Let's create a custom
// Decorate the logger config so that output is always pretty
153
-
.decorate(
154
-
loggingModule.keys.loggerConfig,
155
-
factory=>container=> ({
156
-
...factory(container),
157
-
pretty: true
158
-
})
159
-
)
160
-
// Decorate the log formatter to append an exclamation mark to all log entries
161
-
.decorate(
162
-
loggingModule.keys.logFormatter,
163
-
factory=>container=> {
164
-
const baseFormatter =factory(container)
165
-
return (level, message, data) =>
166
-
baseFormatter(level, message, data) +'!'
167
-
}
168
-
)
169
-
// Overwrite the log writer with some other implementation
170
-
.decorate(
171
-
loggingModule.keys.logWriter,
172
-
() => () =>myCustomLogWriter
173
-
)
152
+
> = {
153
+
key: Symbol('customLoggingModule'),
154
+
build: builder=>builder
155
+
.use(loggingModule.default)
156
+
// Decorate the logger config so that output is always pretty
157
+
.decorate(
158
+
loggingModule.keys.loggerConfig,
159
+
factory=>container=> ({
160
+
...factory(container),
161
+
pretty: true
162
+
})
163
+
)
164
+
// Decorate the log formatter to append an exclamation mark to all log entries
165
+
.decorate(
166
+
loggingModule.keys.logFormatter,
167
+
factory=>container=> {
168
+
const baseFormatter =factory(container)
169
+
return (level, message, data) =>
170
+
baseFormatter(level, message, data) +'!'
171
+
}
172
+
)
173
+
// Overwrite the log writer with some other implementation
174
+
.decorate(
175
+
loggingModule.keys.logWriter,
176
+
() => () =>myCustomLogWriter
177
+
)
178
+
}
174
179
```
175
180
176
181
We can also use decorators to achieve features that aren't explicitly implemented in this library, such as service tagging, which we can do by defining a service as an array:
@@ -188,24 +193,30 @@ type ServiceMap = {
188
193
189
194
const myModule:ContainerModule<
190
195
ServiceMap
191
-
> =builder=>builder
192
-
.define(
193
-
serviceTag,
194
-
() => []
195
-
)
196
+
> = {
197
+
key: Symbol('myModule'),
198
+
build: builder=>builder
199
+
.define(
200
+
serviceTag,
201
+
() => []
202
+
)
203
+
}
196
204
197
205
const myOtherModule:ContainerModule<
198
206
ServiceMap
199
-
> =builder=>builder
200
-
.use(myModule)
201
-
.decorate(
202
-
serviceTag,
203
-
// Add a service to the array of already defined services
204
-
factory=>container=> [
205
-
...factory(container),
206
-
{ foo: 'bar' }
207
-
]
208
-
)
207
+
> = {
208
+
key: Symbol('myOtherModule'),
209
+
build: builder=>builder
210
+
.use(myModule)
211
+
.decorate(
212
+
serviceTag,
213
+
// Add a service to the array of already defined services
214
+
factory=>container=> [
215
+
...factory(container),
216
+
{ foo: 'bar' }
217
+
]
218
+
)
219
+
}
209
220
```
210
221
211
222
And that's it - unlike some other DI containers that claim to be lightweight, tsinject really is tiny and has a simple API, allowing large and complex but loosely coupled applications to be built from small, simple and easily testable components.
0 commit comments