Skip to content

Commit

Permalink
Merge pull request #43 from Roaders/main
Browse files Browse the repository at this point in the history
Allow use of tokens with @optional decorator or optional api calls
  • Loading branch information
Davidhanson90 authored May 7, 2021
2 parents ed283e2 + 9689354 commit b4d6831
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 8 deletions.
2 changes: 1 addition & 1 deletion main/contracts/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ export interface IInjector {
/***
* Gets an instance of a type or returns undefined if no registration
*/
getOptional<T>(type: T): InstanceOfType<T> | undefined;
getOptional<T>(type: T | StringOrSymbol): InstanceOfType<T> | undefined;

/**
* Returns an Array of the all types registered in the container
Expand Down
8 changes: 6 additions & 2 deletions main/core/injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export class Injector implements IInjector {
/**
* Resolves a type and optional returns undefined if no registrations present
*/
public getOptional<T>(type: T): InstanceOfType<T> | undefined {
public getOptional<T>(type: T | StringOrSymbol): InstanceOfType<T> | undefined {
return this.getImpl((type as unknown) as Newable, [], { mode: 'optional' });
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions main/core/util.functions.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -28,8 +28,8 @@ export function get<T>(
* Gets an instance of a type or returns undefined if no registration
* @param type The type to be resolved
*/
export function getOptional<T extends Newable>(type: T): InstanceOfType<T> | undefined {
return getRootInjector().getOptional(type);
export function getOptional<T extends Newable>(type: T | StringOrSymbol): InstanceOfType<T> | undefined {
return getRootInjector().getOptional<T>(type);
}

/***
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
17 changes: 17 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
26 changes: 26 additions & 0 deletions spec/injection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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 = '';
Expand Down

0 comments on commit b4d6831

Please sign in to comment.