diff --git a/libs/core/src/injection-tokens/config.factory.spec.ts b/libs/core/src/injection-tokens/config.factory.spec.ts new file mode 100644 index 0000000000..f5fccf3bbc --- /dev/null +++ b/libs/core/src/injection-tokens/config.factory.spec.ts @@ -0,0 +1,41 @@ +import { faker } from '@faker-js/faker/locale/en_US'; + +import { DaffConfigInjectionToken } from '@daffodil/core'; + +import { createConfigInjectionToken } from './config.factory'; + +interface Config { + field: string; + other: string; +} + +describe('@daffodil/core | createConfigInjectionToken', () => { + let name: string; + let value: number; + let defaultConfig: Config; + + let result: DaffConfigInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + defaultConfig = { + field: faker.random.word(), + other: faker.random.word(), + }; + result = createConfigInjectionToken(defaultConfig, name); + }); + + it('should return a token', () => { + expect(result.token.toString()).toContain(name); + }); + + it('should return a provider that spreads in passed values with the default', () => { + const val = faker.random.word(); + const res = result.provider({ + field: val, + }); + expect(res.provide).toEqual(result.token); + expect(res.useValue.field).toEqual(val); + expect(res.useValue.other).toEqual(defaultConfig.other); + }); +}); diff --git a/libs/core/src/injection-tokens/config.factory.ts b/libs/core/src/injection-tokens/config.factory.ts new file mode 100644 index 0000000000..6cb8df127a --- /dev/null +++ b/libs/core/src/injection-tokens/config.factory.ts @@ -0,0 +1,38 @@ +import { InjectionToken } from '@angular/core'; + +import { DaffConfigInjectionToken } from './config.type'; +import { + TokenDesc, + TokenOptions, +} from './token-constuctor-params.type'; + +/** + * Creates an injection token/provider pair for a DI token that holds a configuration. + * + * See {@link DaffConfigInjectionToken}. + */ +export const createConfigInjectionToken = ( + defaultConfig: T, + desc: TokenDesc, + options?: Partial>, +): DaffConfigInjectionToken => { + const token = new InjectionToken( + desc, + { + factory: () => defaultConfig, + ...options, + }, + ); + const provider = (config: Partial) => ({ + provide: token, + useValue: { + ...defaultConfig, + ...config, + }, + }); + + return { + token, + provider, + }; +}; diff --git a/libs/core/src/injection-tokens/config.type.ts b/libs/core/src/injection-tokens/config.type.ts new file mode 100644 index 0000000000..350f88bc46 --- /dev/null +++ b/libs/core/src/injection-tokens/config.type.ts @@ -0,0 +1,22 @@ +import { + InjectionToken, + ValueProvider, +} from '@angular/core'; + +/** + * A injection token to hold and provide a config value. + */ +export interface DaffConfigInjectionToken { + /** + * The injection token. + * Its default value is the default config passed during token creation. + */ + token: InjectionToken; + + /** + * A helper function to provide a value to the token. + * It will shallow merge the passed config with the default config + * with the passed config keys taking precedence. + */ + provider: (config: Partial) => ValueProvider; +} diff --git a/libs/core/src/injection-tokens/multi.factory.spec.ts b/libs/core/src/injection-tokens/multi.factory.spec.ts new file mode 100644 index 0000000000..d43b5e1bb7 --- /dev/null +++ b/libs/core/src/injection-tokens/multi.factory.spec.ts @@ -0,0 +1,71 @@ +import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker/locale/en_US'; + +import { DaffMultiInjectionToken } from '@daffodil/core'; + +import { createMultiInjectionToken } from './multi.factory'; + +describe('@daffodil/core | createMultiInjectionToken', () => { + let name: string; + let values: Array; + + let result: DaffMultiInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + values = [ + faker.datatype.number(), + faker.datatype.number(), + ]; + result = createMultiInjectionToken(name); + }); + + it('should return a token', () => { + expect(result.token.toString()).toContain(name); + }); + + it('should return a provider', () => { + const res = result.provider(...values); + values.forEach((value, i) => { + expect(res[i].provide).toEqual(result.token); + expect(res[i].useValue).toEqual(value); + }); + }); +}); + +describe('@daffodil/core | createMultiInjectionToken | Integration', () => { + let name: string; + let values: Array; + + let result: DaffMultiInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + values = [ + faker.datatype.number(), + faker.datatype.number(), + ]; + + result = createMultiInjectionToken(name); + }); + + describe('when values are provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + result.provider(...values), + ], + }); + }); + + it('should inject the values', () => { + expect(TestBed.inject(result.token)).toEqual(values); + }); + }); + + describe('when values are not provided', () => { + it('should inject an empty array', () => { + expect(TestBed.inject(result.token)).toEqual([]); + }); + }); +}); diff --git a/libs/core/src/injection-tokens/multi.factory.ts b/libs/core/src/injection-tokens/multi.factory.ts new file mode 100644 index 0000000000..79c90c99dc --- /dev/null +++ b/libs/core/src/injection-tokens/multi.factory.ts @@ -0,0 +1,39 @@ +import { InjectionToken } from '@angular/core'; + +import { DaffMultiInjectionToken } from './multi.type'; +import { + TokenDesc, + TokenOptions, +} from './token-constuctor-params.type'; + +// having a single instance of the default factory +// will hopefully reduce memory footprint +const defaultFactory = () => []; + +/** + * Creates an injection token/provider pair for a multi valued DI token. + * + * See {@link DaffMultiInjectionToken}. + */ +export const createMultiInjectionToken = ( + desc: TokenDesc>, + options?: Partial>>, +): DaffMultiInjectionToken => { + const token = new InjectionToken>( + desc, + { + factory: defaultFactory, + ...options, + }, + ); + const provider = (...values: Array) => values.map((value) => ({ + provide: token, + useValue: value, + multi: true, + })); + + return { + token, + provider, + }; +}; diff --git a/libs/core/src/injection-tokens/multi.type.ts b/libs/core/src/injection-tokens/multi.type.ts new file mode 100644 index 0000000000..1d4e3f8ad8 --- /dev/null +++ b/libs/core/src/injection-tokens/multi.type.ts @@ -0,0 +1,20 @@ +import { + InjectionToken, + ValueProvider, +} from '@angular/core'; + +/** + * A injection token to hold and provide multiple values. + */ +export interface DaffMultiInjectionToken { + /** + * The injection token. + * Its default value is an empty array. + */ + token: InjectionToken>; + + /** + * A helper function to provide values to the token. + */ + provider: (...values: Array) => Array; +} diff --git a/libs/core/src/injection-tokens/public_api.ts b/libs/core/src/injection-tokens/public_api.ts new file mode 100644 index 0000000000..c48618ca26 --- /dev/null +++ b/libs/core/src/injection-tokens/public_api.ts @@ -0,0 +1,8 @@ +export * from './single.type'; +export * from './single.factory'; +export * from './multi.type'; +export * from './multi.factory'; +export * from './config.type'; +export * from './config.factory'; +export * from './services.type'; +export * from './services.factory'; diff --git a/libs/core/src/injection-tokens/services.factory.spec.ts b/libs/core/src/injection-tokens/services.factory.spec.ts new file mode 100644 index 0000000000..55813f7acc --- /dev/null +++ b/libs/core/src/injection-tokens/services.factory.spec.ts @@ -0,0 +1,97 @@ +import { + Injectable, + Type, +} from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { faker } from '@faker-js/faker/locale/en_US'; + +import { DaffServicesInjectionToken } from '@daffodil/core'; + +import { createServicesInjectionToken } from './services.factory'; + +interface TestType { + get(): string; +} + +@Injectable({ + providedIn: 'root', +}) +class Test1 implements TestType { + get() { + return 'test1'; + } +} + +@Injectable({ + providedIn: 'root', +}) +class Test2 implements TestType { + get() { + return 'test2'; + } +} + +describe('@daffodil/core | createServicesInjectionToken', () => { + let name: string; + let values: Array>; + + let result: DaffServicesInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + values = [ + Test1, + Test2, + ]; + result = createServicesInjectionToken(name); + }); + + it('should return a token', () => { + expect(result.token.toString()).toContain(name); + }); + + it('should return a provider', () => { + const res = result.provider(...values); + values.forEach((value, i) => { + expect(res[i].provide).toEqual(result.token); + expect(res[i].useExisting).toEqual(value); + }); + }); +}); + +describe('@daffodil/core | createServicesInjectionToken | Integration', () => { + let name: string; + let values: Array>; + + let result: DaffServicesInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + values = [ + Test1, + Test2, + ]; + result = createServicesInjectionToken(name); + }); + + describe('when values are provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + result.provider(...values), + ], + }); + }); + + it('should inject the values', () => { + expect(TestBed.inject(result.token)[0].get()).toEqual('test1'); + expect(TestBed.inject(result.token)[1].get()).toEqual('test2'); + }); + }); + + describe('when values are not provided', () => { + it('should inject an empty array', () => { + expect(TestBed.inject(result.token)).toEqual([]); + }); + }); +}); diff --git a/libs/core/src/injection-tokens/services.factory.ts b/libs/core/src/injection-tokens/services.factory.ts new file mode 100644 index 0000000000..40aeaaeaea --- /dev/null +++ b/libs/core/src/injection-tokens/services.factory.ts @@ -0,0 +1,42 @@ +import { + InjectionToken, + Type, +} from '@angular/core'; + +import { DaffServicesInjectionToken } from './services.type'; +import { + TokenDesc, + TokenOptions, +} from './token-constuctor-params.type'; + +// having a single instance of the default factory +// will hopefully reduce memory footprint +const defaultFactory = () => []; + +/** + * Creates an injection token/provider pair for a DI token that holds services. + * + * See {@link DaffServicesInjectionToken}. + */ +export const createServicesInjectionToken = ( + desc: TokenDesc>, + options?: Partial>>, +): DaffServicesInjectionToken => { + const token = new InjectionToken>( + desc, + { + factory: defaultFactory, + ...options, + }, + ); + const provider = (...classes: Array>) => classes.map((klass) => ({ + provide: token, + useExisting: klass, + multi: true, + })); + + return { + token, + provider, + }; +}; diff --git a/libs/core/src/injection-tokens/services.type.ts b/libs/core/src/injection-tokens/services.type.ts new file mode 100644 index 0000000000..de428866b9 --- /dev/null +++ b/libs/core/src/injection-tokens/services.type.ts @@ -0,0 +1,21 @@ +import { + ExistingProvider, + InjectionToken, + Type, +} from '@angular/core'; + +/** + * A injection token to hold and provide multiple services. + */ +export interface DaffServicesInjectionToken { + /** + * The injection token. + * Its default value is an empty array. + */ + token: InjectionToken>; + + /** + * A helper function to provide service classes to the token. + */ + provider: (...classes: Array>) => Array; +} diff --git a/libs/core/src/injection-tokens/single.factory.spec.ts b/libs/core/src/injection-tokens/single.factory.spec.ts new file mode 100644 index 0000000000..78854df3f8 --- /dev/null +++ b/libs/core/src/injection-tokens/single.factory.spec.ts @@ -0,0 +1,28 @@ +import { faker } from '@faker-js/faker/locale/en_US'; + +import { DaffSingleInjectionToken } from '@daffodil/core'; + +import { createSingleInjectionToken } from './single.factory'; + +describe('@daffodil/core | createSingleInjectionToken', () => { + let name: string; + let value: number; + + let result: DaffSingleInjectionToken; + + beforeEach(() => { + name = faker.random.word(); + value = faker.datatype.number(); + result = createSingleInjectionToken(name); + }); + + it('should return a token', () => { + expect(result.token.toString()).toContain(name); + }); + + it('should return a provider', () => { + const res = result.provider(value); + expect(res.provide).toEqual(result.token); + expect(res.useValue).toEqual(value); + }); +}); diff --git a/libs/core/src/injection-tokens/single.factory.ts b/libs/core/src/injection-tokens/single.factory.ts new file mode 100644 index 0000000000..dd950f9555 --- /dev/null +++ b/libs/core/src/injection-tokens/single.factory.ts @@ -0,0 +1,21 @@ +import { InjectionToken } from '@angular/core'; + +import { DaffSingleInjectionToken } from './single.type'; + +/** + * Creates an injection token/provider pair for a single valued DI token. + * + * See {@link DaffSingleInjectionToken}. + */ +export const createSingleInjectionToken = (...args: ConstructorParameters>): DaffSingleInjectionToken => { + const token = new InjectionToken(...args); + const provider = (value: R) => ({ + provide: token, + useValue: value, + }); + + return { + token, + provider, + }; +}; diff --git a/libs/core/src/injection-tokens/single.type.ts b/libs/core/src/injection-tokens/single.type.ts new file mode 100644 index 0000000000..15f89d39cb --- /dev/null +++ b/libs/core/src/injection-tokens/single.type.ts @@ -0,0 +1,19 @@ +import { + InjectionToken, + ValueProvider, +} from '@angular/core'; + +/** + * A injection token to hold and provide a single value. + */ +export interface DaffSingleInjectionToken { + /** + * The injection token. + */ + token: InjectionToken; + + /** + * A helper function to provide a value to the token. + */ + provider: (value: R) => ValueProvider; +} diff --git a/libs/core/src/injection-tokens/token-constuctor-params.type.ts b/libs/core/src/injection-tokens/token-constuctor-params.type.ts new file mode 100644 index 0000000000..819d03c097 --- /dev/null +++ b/libs/core/src/injection-tokens/token-constuctor-params.type.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from '@angular/core'; + +export type TokenDesc = ConstructorParameters>[0]; +export type TokenOptions = ConstructorParameters>[1]; diff --git a/libs/core/src/public_api.ts b/libs/core/src/public_api.ts index bb892d1ac1..57ccac0cd9 100644 --- a/libs/core/src/public_api.ts +++ b/libs/core/src/public_api.ts @@ -15,5 +15,6 @@ export * from './collection/public_api'; export * from './identifiable/public_api'; export * from './filterable/public_api'; export * from './filters/public_api'; +export * from './injection-tokens/public_api'; export { DaffOrderable } from './orderable/orderable';