diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index b55c5c38..aeff6197 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -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 { + // (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; @@ -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 { - // (undocumented) - _kind: FeatureKind; - // (undocumented) - _providers: Provider[]; -} - -// @internal -const enum CoreFeatureKind { - // (undocumented) - Defaults = 0 -} - // @internal (undocumented) export const _formatDevMessage: (message: string, options: _FormatDevMessageOptions) => string; @@ -210,9 +215,15 @@ export type MetadataValues = object; // @public export class NgxMetaCoreModule { - static forRoot(options?: { - defaults?: MetadataValues; - }): ModuleWithProviders; + // 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; + // @deprecated + static forRoot(options: NgxMetaCoreModuleForRootOptions): ModuleWithProviders; +} + +// @public @deprecated +export interface NgxMetaCoreModuleForRootOptions { + defaults?: MetadataValues; } // @public @@ -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) => EnvironmentProviders; +export const provideNgxMetaCore: (...features: __CoreFeatures) => EnvironmentProviders; // @public export const provideNgxMetaJsonLd: () => Provider[]; @@ -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; +export const withNgxMetaDefaults: (defaults: MetadataValues) => __CoreFeature<__CoreFeatureKind.Defaults>; // (No @packageDocumentation comment for this package) diff --git a/projects/ngx-meta/docs/content/guides/defaults.md b/projects/ngx-meta/docs/content/guides/defaults.md index 355ca801..ec14d2f1 100644 --- a/projects/ngx-meta/docs/content/guides/defaults.md +++ b/projects/ngx-meta/docs/content/guides/defaults.md @@ -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), + ) ], // ... }) @@ -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 = { diff --git a/projects/ngx-meta/example-apps/templates/module/src/app/app.module.template.ts b/projects/ngx-meta/example-apps/templates/module/src/app/app.module.template.ts index 91187c11..d82d7b03 100644 --- a/projects/ngx-meta/example-apps/templates/module/src/app/app.module.template.ts +++ b/projects/ngx-meta/example-apps/templates/module/src/app/app.module.template.ts @@ -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' @@ -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, diff --git a/projects/ngx-meta/src/core/index.ts b/projects/ngx-meta/src/core/index.ts index 8c155996..0350551c 100644 --- a/projects/ngx-meta/src/core/index.ts +++ b/projects/ngx-meta/src/core/index.ts @@ -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' diff --git a/projects/ngx-meta/src/core/src/core-feature.ts b/projects/ngx-meta/src/core/src/core-feature.ts new file mode 100644 index 00000000..914ccc98 --- /dev/null +++ b/projects/ngx-meta/src/core/src/core-feature.ts @@ -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 { + _kind: FeatureKind + _providers: Provider[] +} + +/** + * @internal + */ +export const __coreFeature = ( + kind: FeatureKind, + providers: Provider[], +): __CoreFeature => ({ + _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 => features.map((f) => f._providers) diff --git a/projects/ngx-meta/src/core/src/defaults-token.ts b/projects/ngx-meta/src/core/src/defaults-token.ts index 3a5083f8..8dcd5914 100644 --- a/projects/ngx-meta/src/core/src/defaults-token.ts +++ b/projects/ngx-meta/src/core/src/defaults-token.ts @@ -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( ngDevMode ? 'NgxMeta Metadata defaults' : 'NgxMetaDefs', ) + +export const injectDefaults = (): MetadataValues | null => + inject(DEFAULTS_TOKEN, { optional: true }) diff --git a/projects/ngx-meta/src/core/src/ngx-meta-core.module.spec.ts b/projects/ngx-meta/src/core/src/ngx-meta-core.module.spec.ts index 16c9e6ab..48d48f6c 100644 --- a/projects/ngx-meta/src/core/src/ngx-meta-core.module.spec.ts +++ b/projects/ngx-meta/src/core/src/ngx-meta-core.module.spec.ts @@ -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) }) }) @@ -30,3 +40,6 @@ const makeSut = ( ) => { TestBed.configureTestingModule({ imports: [moduleWithProviders] }) } + +const injectDefaultsForTesting = (): ReturnType => + TestBed.runInInjectionContext(injectDefaults) diff --git a/projects/ngx-meta/src/core/src/ngx-meta-core.module.ts b/projects/ngx-meta/src/core/src/ngx-meta-core.module.ts index e2e23c42..f6d688be 100644 --- a/projects/ngx-meta/src/core/src/ngx-meta-core.module.ts +++ b/projects/ngx-meta/src/core/src/ngx-meta-core.module.ts @@ -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 + + /** + * 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 + + // noinspection JSDeprecatedSymbols + static forRoot( + optionsOrFeature: + | NgxMetaCoreModuleForRootOptions + | __CoreFeature<__CoreFeatureKind> = {}, + ...features: __CoreFeatures ): ModuleWithProviders { + 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 +} diff --git a/projects/ngx-meta/src/core/src/provide-ngx-meta-core.ts b/projects/ngx-meta/src/core/src/provide-ngx-meta-core.ts index 07e66125..b4e5d985 100644 --- a/projects/ngx-meta/src/core/src/provide-ngx-meta-core.ts +++ b/projects/ngx-meta/src/core/src/provide-ngx-meta-core.ts @@ -1,21 +1,16 @@ -import { - EnvironmentProviders, - makeEnvironmentProviders, - Provider, -} from '@angular/core' -import { MetadataValues } from './metadata-values' -import { DEFAULTS_TOKEN } from './defaults-token' +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core' +import { __CoreFeatures, __providersFromCoreFeatures } from './core-feature' import { CORE_PROVIDERS } from './core-providers' /** * Adds core services of the library to the application. * - * For module-based apps, use {@link NgxMetaCoreModule.forRoot} instead + * For module-based apps, you may use {@link NgxMetaCoreModule.(forRoot:1)} instead. * - * Allows specifying some default metadata values. Keep reading. + * Configures also extra features. Keep reading to see some examples. * * @example - * To specify some default metadata values, use {@link withNgxMetaDefaults} + * Default metadata values can be set up with {@link withNgxMetaDefaults}. * * ```typescript * provideNgxMetaCore( @@ -23,58 +18,17 @@ import { CORE_PROVIDERS } from './core-providers' * ) * ``` * - * @param features - Features to configure the core with. Currently just {@link withNgxMetaDefaults} available + * @see {@link withNgxMetaDefaults} + * @see {@link https://ngx-meta.dev/guides/defaults/ | Defaults guide} + * + * @param features - Features to configure * * @public */ export const provideNgxMetaCore = ( - ...features: ReadonlyArray + ...features: __CoreFeatures ): EnvironmentProviders => makeEnvironmentProviders([ ...CORE_PROVIDERS, - ...features.map((feature) => feature._providers), - ]) - -/** - * Inspired from Angular's router - * - * https://github.com/angular/angular/blob/17.0.7/packages/router/src/provide_router.ts#L80-L96 - * @internal - */ -const enum CoreFeatureKind { - Defaults, -} - -/** - * @internal - */ -interface CoreFeature { - _kind: FeatureKind - _providers: Provider[] -} - -const coreFeature = ( - kind: FeatureKind, - providers: Provider[], -): CoreFeature => ({ - _kind: kind, - _providers: providers, -}) - -/** - * Allows to configure default metadata values. - * - * Use it as part of {@link provideNgxMetaCore} - * - * For module-based apps, check out {@link NgxMetaCoreModule.forRoot} - * - * @param defaults - Default metadata values to use - * - * @public - */ -export const withNgxMetaDefaults = ( - defaults: MetadataValues, -): CoreFeature => - coreFeature(CoreFeatureKind.Defaults, [ - { provide: DEFAULTS_TOKEN, useValue: defaults }, + ...__providersFromCoreFeatures(features), ]) diff --git a/projects/ngx-meta/src/core/src/with-ngx-meta-defaults.ts b/projects/ngx-meta/src/core/src/with-ngx-meta-defaults.ts new file mode 100644 index 00000000..dd9c5cc0 --- /dev/null +++ b/projects/ngx-meta/src/core/src/with-ngx-meta-defaults.ts @@ -0,0 +1,28 @@ +import { MetadataValues } from './metadata-values' +import { __coreFeature, __CoreFeature, __CoreFeatureKind } from './core-feature' +import { DEFAULTS_TOKEN } from './defaults-token' + +/** + * Sets up default metadata values. + * + * When setting metadata values for a page, default values will be used as + * fallback when a metadata value isn't specified. + * + * See also: + * + * - {@link provideNgxMetaCore} to use it with standalone APIs + * + * - {@link NgxMetaCoreModule.(forRoot:1)} to use it with module based APIs + * + * - Defaults guide in {@link https://ngx-meta.dev/guides/defaults/} + * + * @param defaults - Default metadata values to use + * + * @public + */ +export const withNgxMetaDefaults = ( + defaults: MetadataValues, +): __CoreFeature<__CoreFeatureKind.Defaults> => + __coreFeature(__CoreFeatureKind.Defaults, [ + { provide: DEFAULTS_TOKEN, useValue: defaults }, + ])