diff --git a/projects/ngx-meta/src/core/src/metadata-resolver.spec.ts b/projects/ngx-meta/src/core/src/metadata-resolver.spec.ts index bd78d245..e9b1bbe1 100644 --- a/projects/ngx-meta/src/core/src/metadata-resolver.spec.ts +++ b/projects/ngx-meta/src/core/src/metadata-resolver.spec.ts @@ -1,6 +1,5 @@ import { TestBed } from '@angular/core/testing' -import { MetadataResolver } from './metadata-resolver' import { MockProvider, MockProviders } from 'ng-mocks' import { enableAutoSpy } from '../../__tests__/enable-auto-spy' import { MetadataJsonResolver } from './metadata-json-resolver' @@ -11,8 +10,13 @@ import { MaybeUndefined } from './maybe-undefined' import { Provider } from '@angular/core' import { DEFAULTS_TOKEN } from './defaults-token' import { makeMetadata } from './make-metadata' +import { + METADATA_RESOLVER, + METADATA_RESOLVER_PROVIDER, + MetadataResolver, +} from './metadata-resolver' -describe('MetadataResolver', () => { +describe('Metadata resolver', () => { enableAutoSpy() describe('get', () => { @@ -51,7 +55,7 @@ describe('MetadataResolver', () => { }) it('should resolve value using values', () => { - sut.get(dummyMetadata, dummyValues) + sut(dummyMetadata, dummyValues) expect(jsonResolver.get).toHaveBeenCalledWith( dummyMetadata, @@ -60,7 +64,7 @@ describe('MetadataResolver', () => { }) it('should return its value', () => { - expect(sut.get(dummyMetadata, dummyValues)).toEqual(value) + expect(sut(dummyMetadata, dummyValues)).toEqual(value) }) }) @@ -73,7 +77,7 @@ describe('MetadataResolver', () => { }) it('should resolve value using route metadata values', () => { - sut.get(dummyMetadata, dummyValues) + sut(dummyMetadata, dummyValues) expect(routeMetadataValues.get).toHaveBeenCalledOnceWith() expect(jsonResolver.get).toHaveBeenCalledWith( @@ -83,7 +87,7 @@ describe('MetadataResolver', () => { }) it('should return value obtained from route metadata values', () => { - expect(sut.get(dummyMetadata, dummyValues)).toEqual(value) + expect(sut(dummyMetadata, dummyValues)).toEqual(value) }) }) @@ -96,13 +100,13 @@ describe('MetadataResolver', () => { }) it('should resolve value using default values', () => { - sut.get(dummyMetadata, dummyValues) + sut(dummyMetadata, dummyValues) expect(jsonResolver.get).toHaveBeenCalledWith(dummyMetadata, defaults) }) it('should return value obtained from defaults', () => { - expect(sut.get(dummyMetadata, dummyValues)).toEqual(value) + expect(sut(dummyMetadata, dummyValues)).toEqual(value) }) }) @@ -126,7 +130,7 @@ describe('MetadataResolver', () => { }) it('should return the merged object, with value props having more priority', () => { - expect(sut.get(dummyMetadata, dummyValues)).toEqual({ + expect(sut(dummyMetadata, dummyValues)).toEqual({ ...routeValueObject, ...valueObject, }) @@ -149,7 +153,7 @@ describe('MetadataResolver', () => { }) it('should return value from values object', () => { - expect(sut.get(dummyMetadata, dummyValues)).toEqual(value) + expect(sut(dummyMetadata, dummyValues)).toEqual(value) }) }) }) @@ -160,15 +164,15 @@ describe('MetadataResolver', () => { injectSpies() }) it('should return nothing', () => { - expect(sut.get(dummyMetadata, dummyValues)).toBeUndefined() + expect(sut(dummyMetadata, dummyValues)).toBeUndefined() }) }) }) }) -function makeSut(opts: { defaults?: MetadataValues } = {}) { +function makeSut(opts: { defaults?: MetadataValues } = {}): MetadataResolver { const providers: Provider[] = [ - MetadataResolver, + METADATA_RESOLVER_PROVIDER, MockProviders(MetadataJsonResolver, RouteMetadataValues), ] if (opts.defaults) { @@ -177,5 +181,5 @@ function makeSut(opts: { defaults?: MetadataValues } = {}) { TestBed.configureTestingModule({ providers, }) - return TestBed.inject(MetadataResolver) + return TestBed.inject(METADATA_RESOLVER) } diff --git a/projects/ngx-meta/src/core/src/metadata-resolver.ts b/projects/ngx-meta/src/core/src/metadata-resolver.ts index d67c12ff..3ef8cf56 100644 --- a/projects/ngx-meta/src/core/src/metadata-resolver.ts +++ b/projects/ngx-meta/src/core/src/metadata-resolver.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Optional } from '@angular/core' +import { FactoryProvider, InjectionToken, Optional } from '@angular/core' import { MetadataJsonResolver } from './metadata-json-resolver' import { MetadataValues } from './metadata-values' import { RouteMetadataValues } from './route-metadata-values' @@ -7,23 +7,26 @@ import { isObject } from './is-object' import { Metadata } from './metadata' import { MaybeUndefined } from './maybe-undefined' -@Injectable({ providedIn: 'root' }) -export class MetadataResolver { - constructor( - private readonly jsonResolver: MetadataJsonResolver, - private readonly routeMetadataValues: RouteMetadataValues, - @Optional() - @Inject(DEFAULTS_TOKEN) - private readonly defaults: MetadataValues | null, - ) {} +export type MetadataResolver = ( + metadata: Metadata, + values: MetadataValues, +) => T | undefined +export const METADATA_RESOLVER = new InjectionToken( + ngDevMode ? 'NgxMeta Metadata Resolver' : 'NgxMetaMR', +) - get(metadata: Metadata, values: MetadataValues): T | undefined { - const value = this.jsonResolver.get(metadata, values) - const routeValue = this.jsonResolver.get( - metadata, - this.routeMetadataValues.get(), - ) - const defaultValue = this.jsonResolver.get(metadata, this.defaults ?? {}) +export const METADATA_RESOLVER_FACTORY: ( + ...deps: Exclude +) => MetadataResolver = + ( + jsonResolver: MetadataJsonResolver, + routeMetadataValues: RouteMetadataValues, + defaults: MetadataValues | null, + ) => + (metadata: Metadata, values: MetadataValues) => { + const value = jsonResolver.get(metadata, values) + const routeValue = jsonResolver.get(metadata, routeMetadataValues.get()) + const defaultValue = jsonResolver.get(metadata, defaults ?? {}) const effectiveValue = isObject(value) && (isObject(routeValue) || isObject(defaultValue)) ? { ...(defaultValue as object), ...(routeValue as object), ...value } @@ -31,4 +34,12 @@ export class MetadataResolver { return effectiveValue as MaybeUndefined } +export const METADATA_RESOLVER_PROVIDER: FactoryProvider = { + provide: METADATA_RESOLVER, + useFactory: METADATA_RESOLVER_FACTORY, + deps: [ + MetadataJsonResolver, + RouteMetadataValues, + [DEFAULTS_TOKEN, new Optional()], + ], } diff --git a/projects/ngx-meta/src/core/src/metadata.service.spec.ts b/projects/ngx-meta/src/core/src/metadata.service.spec.ts index a2a3c9cc..ca9820fa 100644 --- a/projects/ngx-meta/src/core/src/metadata.service.spec.ts +++ b/projects/ngx-meta/src/core/src/metadata.service.spec.ts @@ -1,9 +1,9 @@ import { TestBed } from '@angular/core/testing' import { MetadataService } from './metadata.service' -import { MockProviders } from 'ng-mocks' +import { MockProvider, MockProviders } from 'ng-mocks' import { makeMetadataProvider } from './__tests__/make-metadata-provider' import { enableAutoSpy } from '../../__tests__/enable-auto-spy' -import { MetadataResolver } from './metadata-resolver' +import { METADATA_RESOLVER, MetadataResolver } from './metadata-resolver' import { RouteMetadataValues } from './route-metadata-values' import { MetadataRegistry } from './metadata-registry' import { MaybeUndefined } from './maybe-undefined' @@ -35,11 +35,11 @@ describe('MetadataService', () => { it('should set each metadata using resolved values', () => { const resolver = TestBed.inject( - MetadataResolver, - ) as unknown as jasmine.SpyObj + METADATA_RESOLVER, + ) as unknown as jasmine.Spy const dummyFirstMetadataValue = 'firstMetadataValue' const dummySecondMetadataValue = 'secondMetadataValue' - resolver.get.and.callFake((definition: Metadata) => { + resolver.and.callFake((definition: Metadata) => { switch (definition) { case firstMetadataProvider.metadata: return dummyFirstMetadataValue as MaybeUndefined @@ -52,15 +52,15 @@ describe('MetadataService', () => { sut.set(dummyValues) expect(metadataRegistry.getAll).toHaveBeenCalledOnceWith() - expect(resolver.get).toHaveBeenCalledTimes(2) - expect(resolver.get).toHaveBeenCalledWith( + expect(resolver).toHaveBeenCalledTimes(2) + expect(resolver).toHaveBeenCalledWith( firstMetadataProvider.metadata, dummyValues, ) expect(firstMetadataProvider.set).toHaveBeenCalledWith( dummyFirstMetadataValue, ) - expect(resolver.get).toHaveBeenCalledWith( + expect(resolver).toHaveBeenCalledWith( secondMetadataProvider.metadata, dummyValues, ) @@ -85,7 +85,8 @@ function makeSut() { TestBed.configureTestingModule({ providers: [ MetadataService, - MockProviders(MetadataRegistry, MetadataResolver, RouteMetadataValues), + MockProviders(MetadataRegistry, RouteMetadataValues), + MockProvider(METADATA_RESOLVER, jasmine.createSpy('Metadata resolver')), ], }) return TestBed.inject(MetadataService) diff --git a/projects/ngx-meta/src/core/src/metadata.service.ts b/projects/ngx-meta/src/core/src/metadata.service.ts index 8e690aa8..940b8125 100644 --- a/projects/ngx-meta/src/core/src/metadata.service.ts +++ b/projects/ngx-meta/src/core/src/metadata.service.ts @@ -1,21 +1,21 @@ -import { Injectable } from '@angular/core' -import { MetadataResolver } from './metadata-resolver' +import { Inject, Injectable } from '@angular/core' import { MetadataValues } from './metadata-values' import { RouteMetadataValues } from './route-metadata-values' import { MetadataRegistry } from './metadata-registry' +import { METADATA_RESOLVER, MetadataResolver } from './metadata-resolver' @Injectable({ providedIn: 'root' }) export class MetadataService { constructor( private readonly registry: MetadataRegistry, - private readonly resolver: MetadataResolver, + @Inject(METADATA_RESOLVER) private readonly resolver: MetadataResolver, private readonly routeValues: RouteMetadataValues, ) {} public set(values: MetadataValues = {}): void { const allMetadata = this.registry.getAll() for (const metadata of allMetadata) { - metadata.set(this.resolver.get(metadata.metadata, values)) + metadata.set(this.resolver(metadata.metadata, values)) } this.routeValues.set(values) } diff --git a/projects/ngx-meta/src/core/src/provide-core.ts b/projects/ngx-meta/src/core/src/provide-core.ts index d0f1ea0e..cd379431 100644 --- a/projects/ngx-meta/src/core/src/provide-core.ts +++ b/projects/ngx-meta/src/core/src/provide-core.ts @@ -6,8 +6,12 @@ import { import { MetadataValues } from './metadata-values' import { DEFAULTS_TOKEN } from './defaults-token' import { HEAD_ELEMENT_UPSERT_OR_REMOVE_PROVIDER } from './head-element-upsert-or-remove' +import { METADATA_RESOLVER_PROVIDER } from './metadata-resolver' -export const CORE_PROVIDERS = [HEAD_ELEMENT_UPSERT_OR_REMOVE_PROVIDER] +export const CORE_PROVIDERS = [ + HEAD_ELEMENT_UPSERT_OR_REMOVE_PROVIDER, + METADATA_RESOLVER_PROVIDER, +] export function provideCore( ...features: ReadonlyArray