From ad56fb9fc65514a09d669996f357fec5b394511c Mon Sep 17 00:00:00 2001 From: GIles Roadnight Date: Fri, 7 May 2021 11:45:41 +0100 Subject: [PATCH 1/3] Allow use of tokens with Optional decorator --- main/contracts/contracts.ts | 2 +- main/core/injector.ts | 8 ++++++-- main/core/util.functions.ts | 6 +++--- spec/injection.spec.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/main/contracts/contracts.ts b/main/contracts/contracts.ts index 062f7ad..9ccd133 100644 --- a/main/contracts/contracts.ts +++ b/main/contracts/contracts.ts @@ -300,7 +300,7 @@ export interface IInjector { /*** * Gets an instance of a type or returns undefined if no registration */ - getOptional(type: T): InstanceOfType | undefined; + getOptional(type: T | StringOrSymbol): InstanceOfType | undefined; /** * Returns an Array of the all types registered in the container diff --git a/main/core/injector.ts b/main/core/injector.ts index b165988..95025af 100644 --- a/main/core/injector.ts +++ b/main/core/injector.ts @@ -360,7 +360,7 @@ export class Injector implements IInjector { /** * Resolves a type and optional returns undefined if no registrations present */ - public getOptional(type: T): InstanceOfType | undefined { + public getOptional(type: T | StringOrSymbol): InstanceOfType | undefined { return this.getImpl((type as unknown) as Newable, [], { mode: 'optional' }); } @@ -448,7 +448,12 @@ export class Injector implements IInjector { ? injector.tokenCache.getTypeForToken(typeOrToken) : typeOrToken; + const allowOptional = options != null && options.mode === 'optional'; + if (constructorType === undefined) { + if (allowOptional) { + return undefined; + } throw new Error(NOT_FOUND); } @@ -463,7 +468,6 @@ export class Injector implements IInjector { // Not in cache so lets try and construct it if (instance == null) { - const allowOptional = options != null && options.mode === 'optional'; const registration = injector.getRegistrationForType(constructorType); // Are we going to use the types resolution strategy over the external one diff --git a/main/core/util.functions.ts b/main/core/util.functions.ts index eb864dd..11d4ace 100644 --- a/main/core/util.functions.ts +++ b/main/core/util.functions.ts @@ -1,5 +1,5 @@ import { DI_ROOT_INJECTOR_KEY } from '../constants/constants'; -import { IConstructionOptions, IInjector, InstanceOfType, Newable } from '../contracts/contracts'; +import { IConstructionOptions, IInjector, InstanceOfType, Newable, StringOrSymbol } from '../contracts/contracts'; import { getGlobal } from './globals'; const globalReference = getGlobal(); @@ -28,8 +28,8 @@ export function get( * Gets an instance of a type or returns undefined if no registration * @param type The type to be resolved */ -export function getOptional(type: T): InstanceOfType | undefined { - return getRootInjector().getOptional(type); +export function getOptional(type: T | StringOrSymbol): InstanceOfType | undefined { + return getRootInjector().getOptional(type); } /*** diff --git a/spec/injection.spec.ts b/spec/injection.spec.ts index 70b6e41..bfc0708 100644 --- a/spec/injection.spec.ts +++ b/spec/injection.spec.ts @@ -1228,12 +1228,26 @@ describe('Injector', () => { expect(child).toBeUndefined(); }); + it('should resolve undefined when getOptional invoked on injector with a token and no registrations', () => { + const instance = getInstance(); + + const child = instance.getOptional('nonExistentToken'); + + expect(child).toBeUndefined(); + }); + it('should resolve undefined when getOptional invoked via pure import and no registrations', () => { const child = getOptional(Child); expect(child).toBeUndefined(); }); + it('should resolve undefined when getOptional invoked via pure import with a token and no registrations', () => { + const child = getOptional('nonExistentToken'); + + expect(child).toBeUndefined(); + }); + it('should resolve instance of type when getOptional invoked on injector has registrations', () => { const instance = getInstance(); @@ -1244,6 +1258,18 @@ describe('Injector', () => { expect(child).toBeDefined(); }); + it('should resolve token when getOptional invoked on injector has registrations', () => { + const instance = getInstance(); + + const token = 'injectionToken'; + + instance.register(Child, { tokens: [token] }); + + const child = instance.getOptional(token); + + expect(child).toBeDefined(); + }); + it('should throw exception if we try and resolve optional type but its children are not registered', () => { const instance = getInstance(); let message = ''; From 18367bde8be863cfe72fde26ffb9c2edb3bf244f Mon Sep 17 00:00:00 2001 From: GIles Roadnight Date: Fri, 7 May 2021 11:47:56 +0100 Subject: [PATCH 2/3] Update readme --- readme.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/readme.md b/readme.md index d390ef4..5f4e748 100644 --- a/readme.md +++ b/readme.md @@ -574,6 +574,23 @@ You can also resolve an optional injectable using the `getOptional` method on th const car = injector.getOptional(Car) //Undefined ``` +## Resolve and optional token + +The `@Optional` decorator and the `getOptional` method also accept tokens to optionally inject: + +```typescript +@Injectable() +class Car { + constructor(@Optional("storageToken") private storage?: Storage) { + console.log(storage) //Undefined + } +} +``` + +```typescript +const car = injector.getOptional("carToken") //Undefined +``` + # Register instance There are sometimes where you do not want the injection container to create the type. Instead you want to take an already existing instance and register it against a type. For this you can use `registerInstance` on the injector. From 9689354a79b2ac7f9289094a4cf7285fd878f3dc Mon Sep 17 00:00:00 2001 From: GIles Roadnight Date: Fri, 7 May 2021 12:07:38 +0100 Subject: [PATCH 3/3] 0.3.10 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51e864f..2b07df2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@morgan-stanley/needle", - "version": "0.3.9", + "version": "0.3.10", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 03d2a07..fab510c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.3.9", + "version": "0.3.10", "description": "A small & lightweight dependency injection container for use in multiple contexts like Angular, React & node.", "name": "@morgan-stanley/needle", "license": "Apache-2.0",