Skip to content

Commit

Permalink
feat: allow feature APIs in core module + deprecate options (#830)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlj95 authored Sep 25, 2024
1 parent 0ef6ed2 commit 47e027e
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 111 deletions.
55 changes: 32 additions & 23 deletions projects/ngx-meta/api-extractor/ngx-meta.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ import { ModuleWithProviders } from '@angular/core';
import { Provider } from '@angular/core';
import { Router } from '@angular/router';

// Warning: (ae-forgotten-export) The symbol "__CoreFeatureKind" needs to be exported by the entry point all-entry-points.d.ts
//
// @internal (undocumented)
interface __CoreFeature<FeatureKind extends __CoreFeatureKind> {
// (undocumented)
_kind: FeatureKind;
// (undocumented)
_providers: Provider[];
}

// @internal
const enum __CoreFeatureKind {
// (undocumented)
Defaults = 0
}

// Warning: (ae-forgotten-export) The symbol "__CoreFeature" needs to be exported by the entry point all-entry-points.d.ts
//
// @internal (undocumented)
type __CoreFeatures = ReadonlyArray<__CoreFeature<__CoreFeatureKind>>;

// @internal (undocumented)
export const __HEAD_ELEMENT_UPSERT_OR_REMOVE_FACTORY: (doc: Document) => (selector: string, element: HTMLElement | null | undefined) => void;

Expand Down Expand Up @@ -52,22 +73,6 @@ export const __TWITTER_CARD_IMAGE_METADATA_SETTER_FACTORY: (metaService: NgxMeta
// @internal (undocumented)
export const _COMPOSED_KEY_VAL_META_DEFINITION_DEFAULT_SEPARATOR = ":";

// Warning: (ae-forgotten-export) The symbol "CoreFeatureKind" needs to be exported by the entry point all-entry-points.d.ts
//
// @internal (undocumented)
interface CoreFeature<FeatureKind extends CoreFeatureKind = CoreFeatureKind> {
// (undocumented)
_kind: FeatureKind;
// (undocumented)
_providers: Provider[];
}

// @internal
const enum CoreFeatureKind {
// (undocumented)
Defaults = 0
}

// @internal (undocumented)
export const _formatDevMessage: (message: string, options: _FormatDevMessageOptions) => string;

Expand Down Expand Up @@ -210,9 +215,15 @@ export type MetadataValues = object;

// @public
export class NgxMetaCoreModule {
static forRoot(options?: {
defaults?: MetadataValues;
}): ModuleWithProviders<NgxMetaCoreModule>;
// Warning: (ae-forgotten-export) The symbol "__CoreFeatures" needs to be exported by the entry point all-entry-points.d.ts
static forRoot(...features: __CoreFeatures): ModuleWithProviders<NgxMetaCoreModule>;
// @deprecated
static forRoot(options: NgxMetaCoreModuleForRootOptions): ModuleWithProviders<NgxMetaCoreModule>;
}

// @public @deprecated
export interface NgxMetaCoreModuleForRootOptions {
defaults?: MetadataValues;
}

// @public
Expand Down Expand Up @@ -408,10 +419,8 @@ export type OpenGraphProfileGender = typeof OPEN_GRAPH_PROFILE_GENDER_FEMALE | t
// @public
export type OpenGraphType = typeof OPEN_GRAPH_TYPE_MUSIC_SONG | typeof OPEN_GRAPH_TYPE_MUSIC_ALBUM | typeof OPEN_GRAPH_TYPE_MUSIC_PLAYLIST | typeof OPEN_GRAPH_TYPE_MUSIC_RADIO_STATION | typeof OPEN_GRAPH_TYPE_VIDEO_MOVIE | typeof OPEN_GRAPH_TYPE_VIDEO_EPISODE | typeof OPEN_GRAPH_TYPE_VIDEO_TV_SHOW | typeof OPEN_GRAPH_TYPE_VIDEO_OTHER | typeof OPEN_GRAPH_TYPE_ARTICLE | typeof OPEN_GRAPH_TYPE_BOOK | typeof OPEN_GRAPH_TYPE_PROFILE | typeof OPEN_GRAPH_TYPE_WEBSITE;

// Warning: (ae-forgotten-export) The symbol "CoreFeature" needs to be exported by the entry point all-entry-points.d.ts
//
// @public
export const provideNgxMetaCore: (...features: ReadonlyArray<CoreFeature>) => EnvironmentProviders;
export const provideNgxMetaCore: (...features: __CoreFeatures) => EnvironmentProviders;

// @public
export const provideNgxMetaJsonLd: () => Provider[];
Expand Down Expand Up @@ -582,7 +591,7 @@ export type TwitterCardType = typeof TWITTER_CARD_TYPE_SUMMARY | typeof TWITTER_
export const _VAL_ATTRIBUTE_CONTENT = "content";

// @public
export const withNgxMetaDefaults: (defaults: MetadataValues) => CoreFeature<CoreFeatureKind.Defaults>;
export const withNgxMetaDefaults: (defaults: MetadataValues) => __CoreFeature<__CoreFeatureKind.Defaults>;

// (No @packageDocumentation comment for this package)

Expand Down
16 changes: 10 additions & 6 deletions projects/ngx-meta/docs/content/guides/defaults.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ This way, everytime you set your metadata values (either using the service or th

--8<-- "includes/module-apps-explanation.md"

Open your `app.module.ts` where [`NgxMetaCoreModule`](ngx-meta.ngxmetacoremodule.md) is imported. Provide your default values by calling [`NgxMetaCoreModule.forRoot`](ngx-meta.ngxmetacoremodule.forroot.md) with the options object.
Open your `app.module.ts` where [`NgxMetaCoreModule`](ngx-meta.ngxmetacoremodule.md) is imported.

Provide your default values by adding a call to [`withNgxMetaDefaults`](ngx-meta.withngxmetadefaults.md) with the default values to set.

```typescript title="app.module.ts"
@NgModule({
// ...
imports: [
// ...
NgxMetaCoreModule.forRoot({
defaults: {
NgxMetaCoreModule.forRoot(
withNgxMetaDefaults({
description: "Awesome products made real ✨"
} satisfies GlobalMetadata
}),
} satisfies GlobalMetadata),
)
],
// ...
})
Expand All @@ -40,7 +42,9 @@ This way, everytime you set your metadata values (either using the service or th

--8<-- "includes/standalone-apps-explanation.md"

Open your `app.config.ts` file where [`provideNgxMetaCore`](ngx-meta.providengxmetacore.md) is provided. Provide your default values by adding a call to [`withNgxMetaDefaults`](ngx-meta.withngxmetadefaults.md) with the default values to set.
Open your `app.config.ts` file where [`provideNgxMetaCore`](ngx-meta.providengxmetacore.md) is provided.

Provide your default values by adding a call to [`withNgxMetaDefaults`](ngx-meta.withngxmetadefaults.md) with the default values to set.

```typescript title="app.config.ts"
export const appConfig: ApplicationConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { RouterOutlet } from '@angular/router'
import { AllMetaSetByServiceComponent } from './all-meta-set-by-service/all-meta-set-by-service.component'
import { AllMetaSetByRouteComponent } from './all-meta-set-by-route/all-meta-set-by-route.component'
import { MetaSetByRouteAndServiceComponent } from './meta-set-by-route-and-service/meta-set-by-route-and-service.component'
import { NgxMetaCoreModule } from '@davidlj95/ngx-meta/core'
import {
NgxMetaCoreModule,
withNgxMetaDefaults,
} from '@davidlj95/ngx-meta/core'
import DEFAULTS_JSON from '@/e2e/cypress/fixtures/defaults.json'
import { NgxMetaRoutingModule } from '@davidlj95/ngx-meta/routing'
import { NgxMetaStandardModule } from '@davidlj95/ngx-meta/standard'
Expand All @@ -34,7 +37,7 @@ import { OneMetaSetByServiceComponent } from './one-meta-set-by-service/one-meta
NgForOf,
RouterOutlet,
JsonPipe,
NgxMetaCoreModule.forRoot({ defaults: DEFAULTS_JSON }),
NgxMetaCoreModule.forRoot(withNgxMetaDefaults(DEFAULTS_JSON)),
NgxMetaRoutingModule.forRoot(),
NgxMetaStandardModule,
NgxMetaOpenGraphModule,
Expand Down
1 change: 1 addition & 0 deletions projects/ngx-meta/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './src/ngx-meta-core.module'
export * from './src/ngx-meta-metadata-loader.module'
export * from './src/provide-ngx-meta-core'
export * from './src/provide-ngx-meta-metadata-loader'
export * from './src/with-ngx-meta-defaults'
// Others
export * from './src/global-metadata'
export * from './src/global-metadata-image'
Expand Down
50 changes: 50 additions & 0 deletions projects/ngx-meta/src/core/src/core-feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Provider } from '@angular/core'

/**
* Inspired from Angular router
*
* https://github.com/angular/angular/blob/17.0.7/packages/router/src/provide_router.ts#L80-L96
* @internal
*/
export const enum __CoreFeatureKind {
Defaults,
}

/**
* @internal
*/
export interface __CoreFeature<FeatureKind extends __CoreFeatureKind> {
_kind: FeatureKind
_providers: Provider[]
}

/**
* @internal
*/
export const __coreFeature = <FeatureKind extends __CoreFeatureKind>(
kind: FeatureKind,
providers: Provider[],
): __CoreFeature<FeatureKind> => ({
_kind: kind,
_providers: providers,
})

/**
* @internal
*/
export const isCoreFeature = (
anObject: object,
): anObject is __CoreFeature<__CoreFeatureKind> =>
('_providers' satisfies keyof __CoreFeature<__CoreFeatureKind>) in anObject

/**
* @internal
*/
export type __CoreFeatures = ReadonlyArray<__CoreFeature<__CoreFeatureKind>>

/**
* @internal
*/
export const __providersFromCoreFeatures = (
features: __CoreFeatures,
): ReadonlyArray<Provider> => features.map((f) => f._providers)
5 changes: 4 additions & 1 deletion projects/ngx-meta/src/core/src/defaults-token.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { InjectionToken } from '@angular/core'
import { inject, InjectionToken } from '@angular/core'
import { MetadataValues } from './metadata-values'

export const DEFAULTS_TOKEN = new InjectionToken<MetadataValues>(
ngDevMode ? 'NgxMeta Metadata defaults' : 'NgxMetaDefs',
)

export const injectDefaults = (): MetadataValues | null =>
inject(DEFAULTS_TOKEN, { optional: true })
29 changes: 21 additions & 8 deletions projects/ngx-meta/src/core/src/ngx-meta-core.module.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { TestBed } from '@angular/core/testing'
import { NgxMetaCoreModule } from './ngx-meta-core.module'
import { DEFAULTS_TOKEN } from './defaults-token'
import { injectDefaults } from './defaults-token'
import { ModuleWithProviders } from '@angular/core'
import { GlobalMetadata } from '@davidlj95/ngx-meta/core'
import { GlobalMetadata } from './global-metadata'
import { withNgxMetaDefaults } from './with-ngx-meta-defaults'

describe('Core module', () => {
const defaults: GlobalMetadata = { title: 'Hello World!' }

it('provides no defaults by default', () => {
makeSut(NgxMetaCoreModule.forRoot())
expect(TestBed.inject(DEFAULTS_TOKEN, null, { optional: true })).toBeNull()

expect(injectDefaultsForTesting()).toBeNull()
})

it('provides no defaults if options object is empty', () => {
// noinspection JSDeprecatedSymbols
makeSut(NgxMetaCoreModule.forRoot({}))
expect(TestBed.inject(DEFAULTS_TOKEN, null, { optional: true })).toBeNull()

expect(injectDefaultsForTesting()).toBeNull()
})

it('provides defaults if specified as options object', () => {
const defaults: GlobalMetadata = { title: 'Hello World!' }
// noinspection JSDeprecatedSymbols
makeSut(NgxMetaCoreModule.forRoot({ defaults }))

expect(TestBed.inject(DEFAULTS_TOKEN, null, { optional: true })).toEqual(
defaults,
)
expect(injectDefaultsForTesting()).toEqual(defaults)
})

it('accepts features from provider APIs, like defaults', () => {
makeSut(NgxMetaCoreModule.forRoot(withNgxMetaDefaults(defaults)))

expect(injectDefaultsForTesting()).toEqual(defaults)
})
})

Expand All @@ -30,3 +40,6 @@ const makeSut = (
) => {
TestBed.configureTestingModule({ imports: [moduleWithProviders] })
}

const injectDefaultsForTesting = (): ReturnType<typeof injectDefaults> =>
TestBed.runInInjectionContext(injectDefaults)
95 changes: 81 additions & 14 deletions projects/ngx-meta/src/core/src/ngx-meta-core.module.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,112 @@
import { ModuleWithProviders, NgModule } from '@angular/core'
import { MetadataValues } from './metadata-values'
import { withNgxMetaDefaults } from './provide-ngx-meta-core'
import { CORE_PROVIDERS } from './core-providers'
import { withNgxMetaDefaults } from './with-ngx-meta-defaults'
import {
__CoreFeature,
__CoreFeatureKind,
__CoreFeatures,
__providersFromCoreFeatures,
isCoreFeature,
} from './core-feature'

/**
* Adds core providers of `ngx-meta` to the application.
* Must use {@link NgxMetaCoreModule.forRoot} method.
* Provides `ngx-meta`'s core library services.
*
* For standalone apps, use {@link provideNgxMetaCore} instead
* Use {@link NgxMetaCoreModule.(forRoot:1)} method. Importing the module alone does nothing.
*
* For standalone apps, use {@link provideNgxMetaCore} instead.
*
* @public
*/
@NgModule()
export class NgxMetaCoreModule {
/**
* Provides the core library services
* Provides `ngx-meta`'s core library services.
*
* Accepts optional features configuration. See examples for more info.
*
* Allows specifying some default metadata values
* Previous configuration of features with an options object has been deprecated.
* See {@link NgxMetaCoreModule.(forRoot:2)} for more information and how to migrate
*
* @example
* Default metadata values can be set up.
*
* ```typescript
* NgxMetaCoreModule.forRoot(withNgxMetaDefaults({title: 'Default title'})
* ```
*
* @see {@link withNgxMetaDefaults}
* @see {@link https://ngx-meta.dev/guides/defaults/}
*
* @param features - Features to configure the core module with
*/
static forRoot(
...features: __CoreFeatures
): ModuleWithProviders<NgxMetaCoreModule>

/**
* Deprecated way of configuring the core module features.
*
* This way of configuring options doesn't allow tree shaking unneeded features.
* So usage is discouraged and deprecated.
* See deprecation notice for the tree-shaking friendly alternative
*
* Checkout the method signature examples for an example on how to migrate to the recommended way
*
* @deprecated Use {@link NgxMetaCoreModule.(forRoot:1)} with feature APIs as arguments instead.
*
* You can set some defaults using the `options` argument
* @example
* ```typescript
* NgxMetaCoreModule.forRoot({defaults: {title: 'Default title'}})
* ```
*
* @param options - Allows providing some default metadata values using `defaults`
* should be migrated to
*
* ```typescript
* NgxMetaCoreModule.forRoot(withNgxMetaDefaults({title: 'Default title'}))
* ```
*/
// noinspection JSDeprecatedSymbols
static forRoot(
options: {
defaults?: MetadataValues
} = {},
options: NgxMetaCoreModuleForRootOptions,
): ModuleWithProviders<NgxMetaCoreModule>

// noinspection JSDeprecatedSymbols
static forRoot(
optionsOrFeature:
| NgxMetaCoreModuleForRootOptions
| __CoreFeature<__CoreFeatureKind> = {},
...features: __CoreFeatures
): ModuleWithProviders<NgxMetaCoreModule> {
const optionFeaturesOrFirstFeature = isCoreFeature(optionsOrFeature)
? [optionsOrFeature]
: optionsOrFeature.defaults
? [withNgxMetaDefaults(optionsOrFeature.defaults)]
: []
return {
ngModule: NgxMetaCoreModule,
providers: [
...CORE_PROVIDERS,
...(options.defaults !== undefined
? withNgxMetaDefaults(options.defaults)._providers
: []),
...__providersFromCoreFeatures([
...optionFeaturesOrFirstFeature,
...features,
]),
],
}
}
}

/**
* Configuration options for {@link NgxMetaCoreModule.(forRoot:2)}
*
* @deprecated Use {@link NgxMetaCoreModule.(forRoot:1)} with feature APIs as arguments instead.
* See {@link NgxMetaCoreModule.(forRoot:2)} for a migration example
* @public
*/
export interface NgxMetaCoreModuleForRootOptions {
/**
* See {@link withNgxMetaDefaults}
*/
defaults?: MetadataValues
}
Loading

0 comments on commit 47e027e

Please sign in to comment.