Skip to content

Commit

Permalink
chore: add util to make (lazy) injection tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlj95 committed Oct 7, 2024
1 parent 67f4527 commit 7449f99
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
3 changes: 3 additions & 0 deletions projects/ngx-meta/api-extractor/ngx-meta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ export interface MakeComposedKeyValMetaDefinitionOptions extends MakeKeyValMetaD
separator?: string;
}

// @internal
export const _makeInjectionToken: <T>(description: string, factory: () => T) => InjectionToken<T>;

// @public
export const makeKeyValMetaDefinition: (keyName: string, options?: MakeKeyValMetaDefinitionOptions) => NgxMetaMetaDefinition;

Expand Down
1 change: 1 addition & 0 deletions projects/ngx-meta/src/core/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { _isDefined } from './is-defined'
export { _makeInjectionToken } from './make-injection-token'
94 changes: 94 additions & 0 deletions projects/ngx-meta/src/core/src/utils/make-injection-token.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
_makeInjectionToken,
INJECTION_TOKEN_FACTORIES,
INJECTION_TOKENS,
} from './make-injection-token'
import { TestBed } from '@angular/core/testing'
import { InjectionToken } from '@angular/core'

describe('make injection token', () => {
const sut = _makeInjectionToken
const description = 'dummy'
const factory = () => description

afterEach(() => {
INJECTION_TOKENS.clear()
INJECTION_TOKEN_FACTORIES.clear()
})

it('should return an injection token using the provided factory', () => {
const factoryOutput = factory()
const injectionToken = sut(description, factory)

expect(TestBed.inject(injectionToken)).toEqual(factoryOutput)
})

it('should return an injection token with given description prefixed by library name', () => {
const injectionToken = sut(description, factory)

expect(injectionToken.toString()).toContain(`ngx-meta ${description}`)
})

describe('when making an already existing token', () => {
let injectionToken: InjectionToken<ReturnType<typeof factory>>

beforeEach(() => {
spyOn(console, 'warn')
injectionToken = sut(description, factory)
})

const shouldNotLogAnyMessage = () =>
it('should not log any message', () => {
expect(console.warn).not.toHaveBeenCalled()
})

describe('when providing another description', () => {
let secondInjectionToken: InjectionToken<ReturnType<typeof factory>>

beforeEach(() => {
secondInjectionToken = sut('another-description', factory)
})

it('should return another injection token', () => {
expect(injectionToken).not.toBe(secondInjectionToken)
})

shouldNotLogAnyMessage()
})

describe('when providing same description and factory', () => {
let secondInjectionToken: InjectionToken<ReturnType<typeof factory>>

beforeEach(() => {
secondInjectionToken = sut(description, factory)
})

it('should return the same injection token', () => {
expect(injectionToken).toBe(secondInjectionToken)
})

shouldNotLogAnyMessage()
})

describe('when providing same description but different factory', () => {
const anotherFactory = () => 'another'
let secondInjectionToken: InjectionToken<
ReturnType<typeof anotherFactory>
>

beforeEach(() => {
secondInjectionToken = sut(description, anotherFactory)
})

it('should return the same injection token if providing same description but different factory', () => {
expect(secondInjectionToken).toBe(injectionToken)
})

it('should warn about it', () => {
expect(console.warn).toHaveBeenCalledWith(
jasmine.stringContaining('same description but different factory'),
)
})
})
})
})
41 changes: 41 additions & 0 deletions projects/ngx-meta/src/core/src/utils/make-injection-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { InjectionToken } from '@angular/core'
import { _formatDevMessage } from '../messaging'
import { MODULE_NAME } from '../module-name'

export const INJECTION_TOKENS = new Map<string, InjectionToken<unknown>>()
export const INJECTION_TOKEN_FACTORIES = new Map<string, () => unknown>()

/**
* See https://github.com/davidlj95/ngx/pull/892
*
* @internal
*/
export const _makeInjectionToken: <T>(
description: string,
factory: () => T,
) => InjectionToken<T> = (description, factory) => {
const injectionToken =
INJECTION_TOKENS.get(description) ??
new InjectionToken(`ngx-meta ${description}`, { factory })
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (ngDevMode) {
if ((INJECTION_TOKEN_FACTORIES.get(description) ?? factory) !== factory) {
console.warn(
_formatDevMessage(
[
'trying to create an injection token with same description but different factory. ',
'The existing injection token will be used and this new factory will be ignored. ',
'This use case is a bit weird anyway. Ensure no duplicate injection tokens are created. ',
].join('\n'),
{
module: MODULE_NAME,
value: description,
},
),
)
}
INJECTION_TOKEN_FACTORIES.set(description, factory)
}
INJECTION_TOKENS.set(description, injectionToken)
return injectionToken
}

0 comments on commit 7449f99

Please sign in to comment.