Skip to content

Commit

Permalink
perf: make metadata resolver a function (#201)
Browse files Browse the repository at this point in the history
* perf: make metadata resolver a function

* refactor: remove "type" suffix from resolver
  • Loading branch information
davidlj95 authored Jan 18, 2024
1 parent 4f922ab commit b0d93cd
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 45 deletions.
32 changes: 18 additions & 14 deletions projects/ngx-meta/src/core/src/metadata-resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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', () => {
Expand Down Expand Up @@ -51,7 +55,7 @@ describe('MetadataResolver', () => {
})

it('should resolve value using values', () => {
sut.get(dummyMetadata, dummyValues)
sut(dummyMetadata, dummyValues)

expect(jsonResolver.get).toHaveBeenCalledWith(
dummyMetadata,
Expand All @@ -60,7 +64,7 @@ describe('MetadataResolver', () => {
})

it('should return its value', () => {
expect(sut.get(dummyMetadata, dummyValues)).toEqual(value)
expect(sut(dummyMetadata, dummyValues)).toEqual(value)
})
})

Expand All @@ -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(
Expand All @@ -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)
})
})

Expand All @@ -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)
})
})

Expand All @@ -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,
})
Expand All @@ -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)
})
})
})
Expand All @@ -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) {
Expand All @@ -177,5 +181,5 @@ function makeSut(opts: { defaults?: MetadataValues } = {}) {
TestBed.configureTestingModule({
providers,
})
return TestBed.inject(MetadataResolver)
return TestBed.inject(METADATA_RESOLVER)
}
45 changes: 28 additions & 17 deletions projects/ngx-meta/src/core/src/metadata-resolver.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -7,28 +7,39 @@ 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<T = unknown> = (
metadata: Metadata,
values: MetadataValues,
) => T | undefined
export const METADATA_RESOLVER = new InjectionToken<MetadataResolver>(
ngDevMode ? 'NgxMeta Metadata Resolver' : 'NgxMetaMR',
)

get<T>(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: <T>(
...deps: Exclude<FactoryProvider['deps'], undefined>
) => MetadataResolver<T> =
(
jsonResolver: MetadataJsonResolver,
routeMetadataValues: RouteMetadataValues,
defaults: MetadataValues | null,
) =>
<T>(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 }
: [value, routeValue, defaultValue].find((v) => v !== undefined)

return effectiveValue as MaybeUndefined<T>
}
export const METADATA_RESOLVER_PROVIDER: FactoryProvider = {
provide: METADATA_RESOLVER,
useFactory: METADATA_RESOLVER_FACTORY,
deps: [
MetadataJsonResolver,
RouteMetadataValues,
[DEFAULTS_TOKEN, new Optional()],
],
}
19 changes: 10 additions & 9 deletions projects/ngx-meta/src/core/src/metadata.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -35,11 +35,11 @@ describe('MetadataService', () => {

it('should set each metadata using resolved values', () => {
const resolver = TestBed.inject(
MetadataResolver,
) as unknown as jasmine.SpyObj<MetadataResolver>
METADATA_RESOLVER,
) as unknown as jasmine.Spy<MetadataResolver>
const dummyFirstMetadataValue = 'firstMetadataValue'
const dummySecondMetadataValue = 'secondMetadataValue'
resolver.get.and.callFake(<T>(definition: Metadata) => {
resolver.and.callFake(<T>(definition: Metadata) => {
switch (definition) {
case firstMetadataProvider.metadata:
return dummyFirstMetadataValue as MaybeUndefined<T>
Expand All @@ -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,
)
Expand All @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions projects/ngx-meta/src/core/src/metadata.service.ts
Original file line number Diff line number Diff line change
@@ -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)
}
Expand Down
6 changes: 5 additions & 1 deletion projects/ngx-meta/src/core/src/provide-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoreFeature>
Expand Down

0 comments on commit b0d93cd

Please sign in to comment.