Skip to content

Commit 29c836e

Browse files
[feat] Added transforms on injection (#136)
* Initial add of pre-resolution interceptor * Finished pre-resolution interceptor * Refactoring for post-resolution, added tests * Add resolveAll() interception * Fix lint issue * Export Frequency * Export Frequency * Address PR feedback * Parameter transform WIP * Initial approach for transformative injections * Completed transformation feature, added docs for interception * Fix lint problem * Export new decorators * Add missing export * PR feedback and make Transform a default export for consistency * Fix missing import
1 parent 891940b commit 29c836e

File tree

12 files changed

+550
-48
lines changed

12 files changed

+550
-48
lines changed

README.md

Lines changed: 134 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@ constructor injection.
1818
- [autoInjectable()](#autoinjectable)
1919
- [inject()](#inject)
2020
- [injectAll()](#injectall)
21+
- [injectWithTransform()](#injectWithTransform)
22+
- [injectAllWithTransform()](#injectAllWithTransform)
2123
- [scoped()](#scoped)
2224
- [Container](#container)
2325
- [Injection Token](#injection-token)
2426
- [Providers](#providers)
2527
- [Register](#register)
2628
- [Registry](#registry)
2729
- [Resolution](#resolution)
30+
- [Interception](#interception)
2831
- [Child Containers](#child-containers)
2932
- [Clearing Instances](#clearing-instances)
3033
- [Circular dependencies](#circular-dependencies)
31-
- [The `delay` helper function](#the-delay-helper-function)
32-
- [Interfaces and circular dependencies](#interfaces-and-circular-dependencies)
34+
- [The `delay` helper function](#the-delay-helper-function)
35+
- [Interfaces and circular dependencies](#interfaces-and-circular-dependencies)
3336
- [Full examples](#full-examples)
3437
- [Example without interfaces](#example-without-interfaces)
3538
- [Example with interfaces](#example-with-interfaces)
@@ -200,22 +203,73 @@ class Bar {
200203
}
201204
```
202205

206+
### injectWithTransform
207+
208+
Parameter decorator which allows for a transformer object to take an action on the resolved object
209+
before returning the result.
210+
211+
```typescript
212+
class FeatureFlags {
213+
public getFlagValue(flagName: string): boolean {
214+
// ...
215+
}
216+
217+
class Foo() {}
218+
219+
class FeatureFlagsTransformer implements Transform<FeatureFlags, bool> {
220+
public transform(flags: FeatureFlags, flag: string) {
221+
return flags.getFlagValue(flag);
222+
}
223+
}
224+
225+
@injectable()
226+
class MyComponent(foo: Foo, @injectWithTransform(FeatureFlags, FeatureFlagsTransformer, "IsBlahEnabled") blahEnabled: boolean){
227+
// ...
228+
}
229+
```
230+
231+
### injectAllWithTransform
232+
233+
This parameter decorator allows for array contents to be passed through a transformer. The transformer can return any type, so this
234+
can be used to map or fold an array.
235+
236+
```typescript
237+
@injectable
238+
class Foo {
239+
public value;
240+
}
241+
242+
class FooTransform implements Transform<Foo[], string[]>{
243+
public transform(foos: Foo[]): string[]{
244+
return foos.map(f => f.value));
245+
}
246+
}
247+
248+
@injectable
249+
class Bar {
250+
constructor(@injectAllWithTransform(Foo, FooTransform) stringArray: string[]) {
251+
// ...
252+
}
253+
}
254+
```
255+
203256
### scoped()
204257
205258
Class decorator factory that registers the class as a scoped dependency within the global container.
206259
207260
#### Available scopes
261+
208262
- Transient
209263
- The **default** registration scope, a new instance will be created with each resolve
210264
- Singleton
211265
- Each resolve will return the same instance (including resolves from child containers)
212266
- ResolutionScoped
213267
- The same instance will be resolved for each resolution of this dependency during a single
214-
resolution chain
268+
resolution chain
215269
- ContainerScoped
216270
- The dependency container will return the same instance each time a resolution for this dependency
217-
is requested. This is similar to being a singleton, however if a child container is made, that child
218-
container will resolve an instance unique to it.
271+
is requested. This is similar to being a singleton, however if a child container is made, that child
272+
container will resolve an instance unique to it.
219273
220274
#### Usage
221275
@@ -237,7 +291,11 @@ form of a Token/Provider pair, so we need to take a brief diversion to discuss t
237291
A token may be either a string, a symbol, a class constructor, or a instance of [`DelayedConstructor`](#circular-dependencies).
238292
239293
```typescript
240-
type InjectionToken<T = any> = constructor<T> | DelayedConstructor<T> | string | symbol;
294+
type InjectionToken<T = any> =
295+
| constructor<T>
296+
| DelayedConstructor<T>
297+
| string
298+
| symbol;
241299
```
242300
243301
### Providers
@@ -286,6 +344,7 @@ to the dependency container.
286344
287345
We have provided 2 factories for you to use, though any function that matches the `FactoryFunction<T>` signature
288346
can be used as a factory:
347+
289348
```typescript
290349
type FactoryFunction<T> = (dependencyContainer: DependencyContainer) => T;
291350
```
@@ -300,27 +359,27 @@ import {instanceCachingFactory} from "tsyringe";
300359

301360
{
302361
token: "SingletonFoo";
303-
useFactory: instanceCachingFactory<Foo>(c => c.resolve(Foo))
362+
useFactory: instanceCachingFactory<Foo>(c => c.resolve(Foo));
304363
}
305364
```
306365
307366
##### predicateAwareClassFactory
308367
309-
This factory is used to provide conditional behavior upon resolution. It caches the result by default, but
368+
This factory is used to provide conditional behavior upon resolution. It caches the result by default, but
310369
has an optional parameter to resolve fresh each time.
311370
312371
```typescript
313372
import {predicateAwareClassFactory} from "tsyringe";
314373

315374
{
316-
token:
317-
useFactory: predicateAwareClassFactory<Foo>(
375+
token: useFactory: predicateAwareClassFactory<Foo>(
318376
c => c.resolve(Bar).useHttps, // Predicate for evaluation
319377
FooHttps, // A FooHttps will be resolved from the container if predicate is true
320378
FooHttp // A FooHttp will be resolved if predicate is false
321-
)
379+
);
322380
}
323381
```
382+
324383
#### Token Provider
325384
326385
```TypeScript
@@ -345,6 +404,7 @@ container.register<Baz>("MyBaz", {useValue: new Baz()});
345404
```
346405
347406
#### Registration options
407+
348408
As an optional parameter to `.register()` you may provide [`RegistrationOptions`](./src/types/registration-options.ts)
349409
which customize how the registration behaves. See the linked source code for up to date documentation
350410
on available options.
@@ -369,7 +429,7 @@ This is useful when you want to [register multiple classes for the same token](#
369429
You can also use it to register and declare objects that wouldn't be imported by anything else,
370430
such as more classes annotated with `@registry` or that are otherwise responsible for registering objects.
371431
Lastly you might choose to use this to register 3rd party instances instead of the `container.register(...)` method.
372-
note: if you want this class to be `@injectable` you must put the decorator before `@registry`, this annotation is not
432+
note: if you want this class to be `@injectable` you must put the decorator before `@registry`, this annotation is not
373433
required though.
374434
375435
### Resolution
@@ -394,15 +454,56 @@ class Foo implements Bar {}
394454
@injectable()
395455
class Baz implements Bar {}
396456
397-
@registry([ // registry is optional, all you need is to use the same token when registering
398-
{ token: 'Bar', useToken: Foo }, // can be any provider
399-
{ token: 'Bar', useToken: Baz },
457+
@registry([
458+
// registry is optional, all you need is to use the same token when registering
459+
{token: "Bar", useToken: Foo}, // can be any provider
460+
{token: "Bar", useToken: Baz}
400461
])
401462
class MyRegistry {}
402463
403464
const myBars = container.resolveAll<Bar>("Bar"); // myBars type is Bar[]
404465
```
405466

467+
### Interception
468+
469+
Interception allows you to register a callback that will be called before or after the resolution of a specific token.
470+
This callback can be registered to execute only once (to perform initialization, for example),
471+
on each resolution to do logging, for example.
472+
473+
`beforeResolution` is used to take an action before an object is resolved.
474+
475+
```typescript
476+
class Bar {}
477+
478+
container.beforeResolution(
479+
Bar,
480+
// Callback signature is (token: InjectionToken<T>, resolutionType: ResolutionType) => void
481+
() => {
482+
console.log("Bar is about to be resolved!");
483+
},
484+
{frequency: "Always"}
485+
);
486+
```
487+
488+
`afterResolution` is used to take an action after the object has been resolved.
489+
490+
```typescript
491+
class Bar {
492+
public init(): void {
493+
// ...
494+
}
495+
}
496+
497+
container.afterResolution(
498+
Bar,
499+
// Callback signature is (token: InjectionToken<T>, result: T | T[], resolutionType: ResolutionType)
500+
(_t, result) => {
501+
result.init();
502+
},
503+
{frequency: "Once"}
504+
);
505+
```
506+
406507
### Child Containers
407508

408509
If you need to have multiple containers that have disparate sets of registrations, you can create child containers:
@@ -465,28 +566,28 @@ export class Foo {
465566
export class Bar {
466567
constructor(public foo: Foo) {}
467568
}
468-
469569
```
470570

471571
Trying to resolve one of the services will end in an error because always one of the constructor will not be fully defined to construct the other one.
472572

473573
```typescript
474-
container.resolve(Foo)
574+
container.resolve(Foo);
475575
```
576+
476577
```
477578
Error: Cannot inject the dependency at position #0 of "Foo" constructor. Reason:
478579
Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function.
479-
```
480-
481-
### The `delay` helper function
580+
```
581+
582+
### The `delay` helper function
482583

483-
The best way to deal with this situation is to do some kind of refactor to avoid the cyclic dependencies. Usually this implies introducing additional services to cut the cycles.
584+
The best way to deal with this situation is to do some kind of refactor to avoid the cyclic dependencies. Usually this implies introducing additional services to cut the cycles.
484585

485-
But when refactor is not an option you can use the `delay` function helper. The `delay` function wraps the constructor in an instance of `DelayedConstructor`.
586+
But when refactor is not an option you can use the `delay` function helper. The `delay` function wraps the constructor in an instance of `DelayedConstructor`.
486587

487-
The *delayed constructor* is a kind of special `InjectionToken` that will eventually be evaluated to construct an intermediate proxy object wrapping a factory for the real object.
488-
489-
When the proxy object is used for the first time it will construct a real object using this factory and any usage will be forwarded to the real object.
588+
The _delayed constructor_ is a kind of special `InjectionToken` that will eventually be evaluated to construct an intermediate proxy object wrapping a factory for the real object.
589+
590+
When the proxy object is used for the first time it will construct a real object using this factory and any usage will be forwarded to the real object.
490591

491592
```typescript
492593
@injectable()
@@ -502,13 +603,11 @@ export class Bar {
502603
// construction of foo is possible
503604
const foo = container.resolve(Foo);
504605

505-
// property bar will hold a proxy that looks and acts as a real Bar instance.
606+
// property bar will hold a proxy that looks and acts as a real Bar instance.
506607
foo.bar instanceof Bar; // true
507-
508608
```
509609
510-
### Interfaces and circular dependencies
511-
610+
### Interfaces and circular dependencies
512611
513612
We can rest in the fact that a `DelayedConstructor` could be used in the same contexts that a constructor and will be handled transparently by tsyringe. Such idea is used in the next example involving interfaces:
514613
@@ -538,7 +637,7 @@ export interface IBar {}
538637
export class Bar implements IBar {
539638
constructor(@inject("IFoo") public foo: IFoo) {}
540639
}
541-
```
640+
```
542641
543642
# Full examples
544643
@@ -619,7 +718,9 @@ const client = container.resolve(Client);
619718
```
620719
621720
## Injecting primitive values (Named injection)
721+
622722
Primitive values can also be injected by utilizing named injection
723+
623724
```typescript
624725
import {singleton, inject} from "tsyringe";
625726

@@ -637,13 +738,15 @@ import {container} from "tsyringe";
637738
import {Foo} from "./foo";
638739

639740
const str = "test";
640-
container.register("SpecialString", { useValue: str });
741+
container.register("SpecialString", {useValue: str});
641742

642743
const instance = container.resolve(Foo);
643744
```
644745
645746
# Non goals
747+
646748
The following is a list of features we explicitly plan on not adding:
749+
647750
- Property Injection
648751
649752
# Contributing

0 commit comments

Comments
 (0)