@davidlj95/ngx-meta
Version:
Set your Angular site's metadata: standard meta tags, Open Graph, Twitter Cards, JSON-LD structured data and more. Supports SSR (and Angular Universal). Use a service. Use routes' data. Set it up in a flash! 🚀
1,226 lines (1,181 loc) • 44.4 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, ENVIRONMENT_INITIALIZER, NgModule, Injectable, makeEnvironmentProviders, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import * as i1 from '@angular/platform-browser';
import { Router } from '@angular/router';
const coreFeature = (kind, providers) => ({
_kind: kind,
_providers: providers,
});
const isCoreFeature = (anObject) => '_providers' in anObject;
const providersFromCoreFeatures = (features) => features.map((f) => f._providers);
/**
* Typescript's type guard helper to ensure a value is neither `null` nor `undefined`.
*
* @param value - Value to check
*
* @internal
*/
const _isDefined = (value) => value !== null && value !== undefined;
/**
* @internal
*/
const _formatDevMessage = (message, options) => {
const header = [`ngx-meta/${options.module}:`, options.property, message]
.filter((s) => !!s)
.join(' ');
const body = options.value ? `-> Value: "${options.value}"` : undefined;
const footer = options.link
? `For more information, see ${options.link}`
: undefined;
return [header, body, footer].filter((s) => !!s).join('\n');
};
/**
* Logs an error message about a URL not being HTTP or HTTPs
*
* Useful to warn developers about some metadata that requires absolute HTTP
* or HTTPs URLs
*
* MUST be used with `ngDevMode` so that this message only runs in development
*
* @internal
*/
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
const _maybeNonHttpUrlDevMessage = (url, opts) => {
const urlStr = url?.toString();
if (urlStr && !(urlStr.startsWith('http') || urlStr.startsWith('https'))) {
const shouldOrMust = opts.shouldInsteadOfMust ? 'should' : 'must';
console.warn(_formatDevMessage(`URL ${shouldOrMust} be absolute and use either http or https`, opts));
}
};
/**
* Logs a warn message when a value string exceeds a length threshold
*
* Useful to warn developers about some metadata improperly used
*
* MUST be used with `ngDevMode` so that this message only runs in development
*
* @internal
*/
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
const _maybeTooLongDevMessage = (value, maxLength, opts) => {
if (value && value.length > maxLength) {
console.warn(_formatDevMessage(`exceeds recommended size of ${maxLength} chars`, opts));
}
};
const MODULE_NAME = 'core';
/** @visibleForTesting */
const INJECTION_TOKENS = new Map();
/** @visibleForTesting */
const INJECTION_TOKEN_FACTORIES = new Map();
/**
* Creates an injection token with the given factory function if it doesn't exist.
* To determine if an injection token exists, the description string is used.
*
* Useful to create {@link _LazyInjectionToken}s.
* \> The function can't be used to create a lazy injection token directly
* \> As a function call won't be tree-shaken. Which is the main purpose of lazy tokens.
* \> More in https://github.com/davidlj95/ngx/pull/902
*
* It also adds the library name as prefix to the injection token description.
* In order to locate library's injectable easily when debugging an Angular project.
*
* @internal
*/
const _makeInjectionToken = (description, factory) => {
const injectionToken = INJECTION_TOKENS.get(description) ??
new InjectionToken(`ngx-meta ${description}`,
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
factory ? { factory } : undefined);
INJECTION_TOKENS.set(description, injectionToken);
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (ngDevMode && factory) {
if ((INJECTION_TOKEN_FACTORIES.get(description)?.toString() ??
factory.toString()) !== factory.toString()) {
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);
}
return injectionToken;
};
/**
* Helper function to combine multiple options (objects).
*
* In case of specifying same options more than once, the latter one will take precedence.
* Provide them sorted by ascendant priority. Less priority options first. Top priority options last.
*
* Can be used to combine options for:
*
* - {@link provideNgxMetaManager}
*
* @param options - Options to combine.
*
* @public
*/
const withOptions = (...options) => options.reduce((acc, curr) => ({ ...acc, ...curr }), {});
const defaults = () => _makeInjectionToken(ngDevMode ? 'Metadata defaults' : 'Defs');
const injectDefaults = () => inject(defaults(), { optional: true });
/**
* 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.
*
* @example
*
* <b>Using standalone, recommended API</b>
* ```typescript
* provideNgxMetaCore(
* withNgxMetaDefaults({title: 'Default title'})
* )
* ```
*
* <b>Using module-based API</b>
* ```typescript
* NgxMetaCoreModule.forRoot(
* withNgxMetaDefaults({title: 'Default title'})
* )
* ```
*
* See also:
*
* - {@link provideNgxMetaCore}: to use it with the standalone, recommended API.
*
* - {@link NgxMetaCoreModule.(forRoot:1)}: to use it with the module-based API.
*
* - {@link https://ngx-meta.dev/guides/defaults/ | Defaults guide}
*
* - {@link https://ngx-meta.dev/guides/metadata-values-json/ | Metadata values JSON guide}
*
* @param defaults - Default metadata values to use
*
* @public
*/
const withNgxMetaDefaults = (defaults$1) => coreFeature(0 /* CoreFeatureKind.Defaults */, [
{ provide: defaults(), useValue: defaults$1 },
]);
/**
* Formats page titles.
*
* The default is to provide the page title as is.
*
* @internal
*/
const _titleFormatter = () => _makeInjectionToken(ngDevMode ? 'Title formatter' : 'TF', () => (title) => title);
/**
* Provides a page title formatter.
*
* The formatter will be called with the specified page title metadata.
* Its output will be the value placed as the page's title by the metadata manager.
* This way you can prepend or append your site name or brand to all page titles, for instance.
*
* Built-in metadata managers that use this formatter are:
*
* - {@link Standard.title}
*
* - {@link OpenGraph.title}
*
* - {@link TwitterCard.title}
*
* @example
*
* <b>Using standalone, recommended API</b>
* ```typescript
* provideNgxMetaCore(
* withNgxMetaTitleFormatter((title) => `${title} - Site name`)
* )
* ```
*
* <b>Using module-based API</b>
* ```typescript
* NgxMetaCoreModule.forRoot(
* withNgxMetaTitleFormatter((title) => `${title} - Site name`)
* )
* ```
*
* See also:
*
* - {@link provideNgxMetaCore}: to use it with the standalone, recommended API.
*
* - {@link NgxMetaCoreModule.(forRoot:1)}: to use it with the module-based API.
*
* - {@link https://ngx-meta.dev/guides/title-formatting/ | Title formatting guide}
*
*
* @param titleFormatter - A function that takes the page title set in metadata values and returns the formatted title
*
* @beta
*/
const withNgxMetaTitleFormatter = (titleFormatter) => coreFeature(2 /* CoreFeatureKind.TitleFormatter */, [
{
provide: _titleFormatter(),
useFactory: () => titleFormatter,
},
]);
/**
* @internal
*/
const _GLOBAL_TITLE = 'title';
/**
* @internal
*/
const _GLOBAL_DESCRIPTION = 'description';
/**
* @internal
*/
const _GLOBAL_APPLICATION_NAME = 'applicationName';
/**
* @internal
*/
const _GLOBAL_CANONICAL_URL = 'canonicalUrl';
/**
* @internal
*/
const _GLOBAL_LOCALE = 'locale';
/**
* @internal
*/
const _GLOBAL_IMAGE = 'image';
/**
* @internal
*/
const _headElementUpsertOrRemove = () => _makeInjectionToken(ngDevMode ? 'Head element upsert or remove util' : 'HEUOR', () => {
const head = inject(DOCUMENT).head;
return (selector, element) => {
const existingScriptElement = head.querySelector(selector);
if (existingScriptElement) {
head.removeChild(existingScriptElement);
}
if (!_isDefined(element)) {
return;
}
head.appendChild(element);
};
});
/**
* Abstract class every metadata manager must implement.
*
* Used as {@link https://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-object | injection token}
* to provide metadata managers the library will take into account.
*
* @remarks
*
* See also:
*
* - {@link https://ngx-meta.dev/guides/manage-your-custom-metadata/ | Manage your custom metadata guide}
*
* - {@link makeMetadataManagerProviderFromSetterFactory} for a helper to create a metadata manager
*
* - {@link provideNgxMetaManager} for an experimental helper to create a metadata manager
*
* @typeParam Value - Value type that can be handled by the setter
*
* @public
*/
class NgxMetaMetadataManager {
}
/**
* @internal
*/
const _injectMetadataManagers = () =>
// https://stackoverflow.com/q/74598049/3263250
inject(NgxMetaMetadataManager, {
optional: true,
}) ?? [];
const metadataRegistryFactory = () => {
const managers = _injectMetadataManagers();
const managersById = new Map();
const register = (manager) => {
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (managersById.has(manager.id)) {
return;
}
managersById.set(manager.id, manager);
};
managers.forEach(register);
const getAll = () => managersById.values();
const findByGlobalOrJsonPath = (globalOrJsonPath) => [...getAll()].filter((manager) => manager.resolverOptions.global == globalOrJsonPath ||
manager.resolverOptions.jsonPath.join('.') == globalOrJsonPath);
return {
register,
getAll,
findByGlobalOrJsonPath,
};
};
const metadataRegistry = () => _makeInjectionToken(ngDevMode ? 'Metadata Registry' : 'MReg', metadataRegistryFactory);
const provideMetadataRegistry = () => ({
provide: metadataRegistry(),
useFactory: metadataRegistryFactory,
});
/**
* Allows to load metadata managers after library has been initialized.
*
* @remarks
*
* This is the standalone, recommended API. Using this API is preferred.
* However, you may also use {@link NgxMetaMetadataLoaderModule} as the Angular module-based equivalent API.
*
* @public
*/
const provideNgxMetaMetadataLoader = () => [
provideMetadataRegistry(),
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory: () => {
const globalRegistry = inject(metadataRegistry(), { skipSelf: true });
const localRegistry = inject(metadataRegistry());
return () => {
const localMetadata = localRegistry.getAll();
for (const metadata of localMetadata) {
globalRegistry.register(metadata);
}
};
},
},
];
/**
* Allows to load metadata managers after library has been initialized.
*
* Check out {@link provideNgxMetaMetadataLoader} for the standalone, recommended API.
*
* @public
*/
class NgxMetaMetadataLoaderModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetadataLoaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetadataLoaderModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetadataLoaderModule, providers: [provideNgxMetaMetadataLoader()] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetadataLoaderModule, decorators: [{
type: NgModule,
args: [{
providers: [provideNgxMetaMetadataLoader()],
}]
}] });
// noinspection JSDeprecatedSymbols
/**
* Creates an Angular {@link https://angular.dev/guide/di/dependency-injection-providers#factory-providers-usefactory | factory provider}
* providing an {@link NgxMetaMetadataManager}.
*
* See {@link https://ngx-meta.dev/guides/manage-your-custom-metadata/ | manage custom metadata guide} for an example.
*
* @deprecated Use {@link provideNgxMetaManager} APIs instead.
* See {@link https://ngx-meta.dev/migrations/02-manager-provider-apis/ } for more information.
*
* @remarks
*
* Factory providers are used for built-in modules instead of Angular services.
* Reason is that code created by `@Injectable` decorator takes many bytes,
* whereas a call to this function creating a factory provider takes fewer.
*
* See {@link https://github.com/davidlj95/ngx/issues/112}
*
* @param setterFactory - Function that creates a {@link NgxMetaMetadataManager} given some dependencies
* @param opts - Options to create the factory
* @public
*/
/* istanbul ignore next - unused. was covered when used */
const makeMetadataManagerProviderFromSetterFactory = (setterFactory, opts) => {
const deps = opts.d ?? [];
return {
provide: NgxMetaMetadataManager,
multi: true,
useFactory: (...deps) => ({
id: opts.id ?? opts.jP.join('.'),
resolverOptions: {
jsonPath: opts.jP,
global: opts.g,
objectMerge: opts.m,
},
set: setterFactory(...deps),
}),
deps,
};
};
/**
* Creates an {@link NgxMetaMetadataManager} provider to manage some metadata.
*
* Check out {@link https://ngx-meta.dev/guides/manage-your-custom-metadata/ | manage your custom metadata guide} to
* learn how to provide your custom metadata managers.
*
* @remarks
*
* Options can be specified using helper functions. {@link withOptions} can be used to combine more than one.
*
* Available option functions:
*
* - {@link withManagerDeps}
*
* - {@link withManagerGlobal}
*
* - {@link withManagerObjectMerging}
*
* @example
*
* ```typescript
* const CUSTOM_TITLE_PROVIDER = provideNgxMetaManager<string | undefined>(
* 'custom.title',
* (metaElementsService: NgxMetaElementsService) => (value) => {
* metaElementsService.set(
* withNameAttribute('custom:title'),
* withContentAttribute(value),
* )
* },
* withOptions(
* withManagerDeps(NgxMetaElementsService),
* withGlobal('title'),
* ),
* )
* ```
*
* @param jsonPath - Path to access the metadata value this manager needs given a JSON object
* containing metadata values. Path is expressed as the keys to use to access the value
* joined by a "." character.
* You can use {@link withManagerJsonPath} to provide an array of keys instead.
* For more information, checkout {@link MetadataResolverOptions.jsonPath}
* @param setterFactory - Factory function that creates the {@link MetadataSetter} function for the manager (which
* manages the metadata element on the page).
* You can inject dependencies either using {@link withManagerDeps} option, that will be passed
* as arguments to the setter factory function. This way is preferred, as takes fewer bytes of
* your bundle size. However, type safety depends on you.
* Or use {@link https://angular.dev/api/core/inject | Angular's `inject` function} for a more
* type-safe option.
* @param options - Extra options for the metadata manager provider creation. Use one of the helpers listed in this
* method's reference docs to supply one or more of them.
* @public
*/
const provideNgxMetaManager = (jsonPath, setterFactory,
/* istanbul ignore next - quite simple */
options = {}) => ({
provide: NgxMetaMetadataManager,
multi: true,
useFactory: (...deps) => ({
id: jsonPath,
set: setterFactory(...deps),
resolverOptions: {
jsonPath: jsonPath.split('.'),
global: options.g,
objectMerge: options.o,
},
}),
deps: options.d,
});
/**
* Specifies dependencies to inject to the setter factory function passed to {@link provideNgxMetaManager}
*
* See also:
*
* - {@link https://angular.dev/guide/di/dependency-injection-providers#factory-providers-usefactory:~:text=property%20is%20an%20array%20of%20provider%20tokens | Factory providers' deps}
*
* - {@link https://angular.dev/api/core/FactoryProvider#deps | FactoryProvider#deps}
*
* @param deps - Dependencies to inject. Each argument declares the dependency to inject.
*
* @public
*/
const withManagerDeps = (...deps) => ({
d: deps,
});
/**
* Sets the global key to use for a metadata manager created with {@link provideNgxMetaManager}
*
* @param global - See {@link MetadataResolverOptions.global}
*
* @public
*/
const withManagerGlobal = (global) => ({ g: global });
/**
* Enables object merging for the manager being created with {@link provideNgxMetaManager}
*
* See {@link MetadataResolverOptions.objectMerge} for more information.
*
* @public
*/
const withManagerObjectMerging = () => ({
o: true,
});
/**
* Transforms a JSON Path specified as an array of keys into a string joined by dots.
*
* Useful to use with {@link provideNgxMetaManager} to avoid repeating same keys around.
*
* @remarks
*
* You can specify a type to ensure the keys are valid. See example below.
*
* Beware that specifying a type won't work if:
*
* The type refers other types and more than 2 levels are specified:
*
* ```typescript
* interface CustomMetadata { custom: Custom }
* interface Custom { moar: Moar }
*
* // 👇❌ Reports incorrect Typescript error about `never` type
* withManagerJsonPath<CustomMetadata>('custom', 'moar', 'foo')
* ```
*
* More than 3 keys are specified:
*
* ```typescript
* interface CustomMetadata {
* custom: {
* moar: {
* moarThanMoar: {
* foo: string
* }
* }
* }
* }
*
* // 👇❌ Reports incorrect Typescript error about `never` type
* withManagerJsonPath<CustomMetadata>('custom', 'moar', 'moarThanMoar', 'foo') //
* ```
*
* Omit the type to skip type checking and its limitations:
*
* ```typescript
* withManagerJsonPath('whatever', 'untyped', 'keys')
* ```
*
* @example
*
* ```typescript
* interface CustomMetadata {
* custom: {
* title: string
* }
* }
*
* withManagerJsonPath<CustomMetadata>('custom','title') // ✅ No error. IDE helps you auto-complete.
* withManagerJsonPath<CustomMetadata>('custom','not-a-prop') // ❌ Typescript error
* withManagerJsonPath('no', 'type', 'checks')
* ```
*
* @param jsonPath - Parts of the JSON Path to join into a string.
*
* @public
*/
const withManagerJsonPath = (...jsonPath) => jsonPath.join('.');
// noinspection JSDeprecatedSymbols
/**
* Creates a {@link NgxMetaMetaDefinition} for its use with {@link NgxMetaMetaService}
* by understanding `<meta>` elements as key / value pair elements.
*
* @remarks
*
* One can think about some `<meta>` elements as key / value pairs.
*
* For instance `<meta name='description' content='Lorem ipsum'>` would
* actually be a key / value pair meta where
* - `description` is the key
* - `Lorem ipsum` is the value
* - `name` is the key's HTML attribute name
* - `content`is the value's HTML attribute name
*
* Value is set by {@link NgxMetaMetaService.set} by providing this model and an
* actual value
*
* @deprecated Use {@link NgxMetaElementsService} APIs instead.
* See {@link https://ngx-meta.dev/migrations/01-meta-element-apis | migration guide} for more info
*
* @param keyName - Name of the key in the key/value meta definition
* @param options - Specifies HTML attribute names and extras of the definition if any
*
* @public
*/
const makeKeyValMetaDefinition = (keyName,
/* istanbul ignore next - quite simple */
options = {}) => {
/* istanbul ignore next - quite simple */
const keyAttr = options.keyAttr ?? 'name';
/* istanbul ignore next - quite simple */
const valAttr = options.valAttr ?? 'content';
return {
withContent: (value) => ({
[keyAttr]: keyName,
[valAttr]: value,
...options.extras,
}),
attrSelector: `${keyAttr}='${keyName}'`,
};
};
// noinspection JSDeprecatedSymbols
/**
* Creates a key / value meta definition ({@link NgxMetaMetaDefinition})
* where the key name is composed by several strings joined by a separator.
*
* See also {@link makeKeyValMetaDefinition}
*
* @example
* For instance, Open Graph's meta definition for property `og:title` (hence
* element `<meta property='og:title'>`) is composed of `og` and `title`.
* Its {@link NgxMetaMetaDefinition} could be created with:
*
* ```typescript
* const ogTitleMetaDefinition = makeComposedKeyValMetaDefinition(
* ['og', 'title'],
* {
* keyAttr: 'property',
* separator: ':', // Could be omitted, as it's the default one
* }
* )
* ```
*
* @deprecated Use {@link NgxMetaElementsService} APIs instead.
* See {@link https://ngx-meta.dev/migrations/01-meta-element-apis | migration guide} for more info
*
* @param names - Names to create they key name
* @param options - Options object
* @public
*/
/* istanbul ignore next - quite simple */
const makeComposedKeyValMetaDefinition = (names, options = {}) => makeKeyValMetaDefinition(names.join(options.separator ?? ':'), options);
// noinspection JSDeprecatedSymbols
/**
* Creates, updates or removes `<meta>` elements.
*
* Uses Angular {@link https://angular.dev/api/platform-browser/Meta | Meta} APIs under the hood.
*
* @deprecated Use {@link NgxMetaElementsService} APIs instead.
* See {@link https://ngx-meta.dev/migrations/01-meta-element-apis | migration guide} for more info
*
* @public
*/
class NgxMetaMetaService {
constructor(meta) {
this.meta = meta;
}
/**
* Creates, updates or removes a specific `<meta>` element.
*
* The element is modeled using a {@link NgxMetaMetaDefinition} object.
*
* The element is created with the provided content. If no content is given, element is removed.
*
* @deprecated Use {@link NgxMetaElementsService} APIs instead.
* See {@link https://ngx-meta.dev/migrations/01-meta-element-apis | migration guide} for more info
*
* @param definition - `<meta>` element to create, update or remove
* @param content - Content value to create or update the `<meta>` element.
* Use `null` or `undefined` to remove the element from the page.
*/
set(definition, content) {
switch (content) {
case undefined:
case null:
this.meta.removeTag(definition.attrSelector);
return;
default:
this.meta.updateTag(definition.withContent(content));
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetaService, deps: [{ token: i1.Meta }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetaService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaMetaService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.Meta }] });
/**
* Manages `<meta>` elements inside `<head>`
*
* @public
*/
class NgxMetaElementsService {
constructor(meta) {
this.meta = meta;
}
/**
* Creates, updates or removes some kind of `<meta>` elements inside `<head>` in a declarative fashion.
*
* Kind of `<meta>` elements to manage are identified by an HTML attribute providing its metadata name.
* For instance, to manage description metadata elements (`<meta name="description">`) on the page, the
* `name` attribute with `description` value identifies them.
*
* Then, contents for those can be specified. In the shape of a key/value JSON object declaring each element's
* additional attributes. Mainly `content` named attributes. See {@link NgxMetaElementAttributes}.
* If no contents are provided, all `<meta>` elements of that kind will be removed.
* An array of contents may be given to create multiple `<meta>` elements with same kind.
*
* @example
* <b>Setting `<meta name="description" content="Cool page"/>`</b>
*
* ```typescript
* ngxMetaElementsService.set(
* withNameAttribute('description'), // same as `['name','description']`
* withContent('Cool page'), // same as `{content:'Cool page'}`
* )
* ```
*
* Utility functions {@link withNameAttribute} and {@link withContentAttribute} help creating the
* name attribute identifying the kind of meta elements and the contents to provide for it.
*
* {@link withContentAttribute} helps to create the attributes key / value object.
*
* <b>Removing any `<meta name="description"/>` existing elements</b>
*
* ```typescript
* ngxMetaElementsService.set(
* withNameAttribute('description'), // same as `['name','description']`
* undefined, // same as `withContent(undefined)`
* )
* ```
*
* <b>Setting many `<meta name="theme-color"/>` elements</b>
*
* ```typescript
* ngxMetaElementsService.set(
* withNameAttribute('theme-color'), // same as `['name','theme-color']`
* [
* withContent('darkblue', { media: "(prefers-color-scheme: dark)" }),
* withContent('lightblue') // same as `{content:'lightblue'}`
* ]
* )
* ```
*
* <b>Removing any `<meta name="theme-color"/>` existing elements</b>
*
* ```typescript
* ngxMetaElementsService.set(
* withNameAttribute('theme-color'), // same as `['name','theme-color']`
* [], // `undefined` is valid too
* )
* ```
*
* Attribute name helpers:
*
* - {@link withNameAttribute}
*
* - {@link withPropertyAttribute}
*
* Content helpers:
*
* - {@link withContentAttribute}
*
* @param nameAttribute - Attribute use to identify which kind of `<meta>` elements to manage.
* As an array with the attribute name in first position and attribute value in second one.
* Utility functions exist to generate arrays for common name attributes.
* See {@link withNameAttribute} and {@link withPropertyAttribute} helpers to create those
* arrays without repeating attribute names around.
*
* @param content - Content(s) attributes to set for this `<meta>` elements kind.
* Or the lack of them to remove all `<meta>` elements of this kind.
* See {@link withContentAttribute} helper for creating content objects.
*/
set(nameAttribute, content) {
const [nameAttributeName, nameAttributeValue] = nameAttribute;
const attrSelector = `${nameAttributeName}="${nameAttributeValue}"`;
this.meta.getTags(attrSelector).forEach((tag) => tag.remove());
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (!content) {
return;
}
const contents = Array.isArray(content)
? content
: [content];
this.meta.addTags(contents.map((content) => ({
[nameAttributeName]: nameAttributeValue,
...content,
})));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaElementsService, deps: [{ token: i1.Meta }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaElementsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaElementsService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.Meta }] });
/**
* Creates an attribute name/value identifying a `<meta name="{value}">` element kind.
*
* See {@link NgxMetaElementsService.set}.
*
* @param value - Value for the `name` attribute of the `<meta>` element
*
* @public
*/
const withNameAttribute = (value) => ['name', value];
/**
* Creates an attribute name/value identifying a `<meta property="{value}">` element kind.
*
* See {@link NgxMetaElementsService.set}.
*
* @param value - Value for the `property` attribute of the `<meta>` element
*
* @public
*/
const withPropertyAttribute = (value) => ['property', value];
/**
* Creates an {@link NgxMetaElementAttributes} object specifying the `content` attribute to the
* given `value`. Plus optional `extras`.
*
* Unless given `value` is `null` or `undefined`. In that case, `undefined` is returned.
*
* See {@link NgxMetaElementsService.set}
*
* @param content - Value for the `property` attribute of the `<meta>` element
* @param extras - Extra attributes to include in the object if `content` is defined.
*
* @public
*/
const withContentAttribute = ((content, extras) => (content ? { content, ...extras } : undefined));
/**
* @internal
*/
const _composedMetadataName = (...names) => names.join(':');
/**
* @internal
*/
const _provideNgxMetaModuleManager = (key, scope, options) => provideNgxMetaManager(withManagerJsonPath(...scope, key), options.f ??
((metaElementsService) => (value) => metaElementsService.set(options.n ?? withNameAttribute(key), withContentAttribute(value))), withOptions(withManagerDeps(options.d ?? [NgxMetaElementsService]), options.k ? withManagerGlobal(key) : {}, options));
/**
* @internal
*/
const _withModuleManagerSetterFactory = (setterFactory) => ({
f: setterFactory,
});
/**
* @internal
*/
const _withModuleManagerNameAttribute = (nameAttribute) => ({
n: nameAttribute,
});
/**
* @internal
*/
const _withModuleManagerSameGlobalKey = () => ({
k: true,
});
/**
* Provides `ngx-meta`'s core library services.
*
* @remarks
*
* This is the standalone, recommended API. Using this API is preferred.
* However, you may also use {@link NgxMetaCoreModule.(forRoot:1)} as the Angular module-based equivalent API.
*
* Allows setting up additional features:
*
* - {@link withNgxMetaDefaults}
*
* - {@link withNgxMetaBaseUrl}
*
* - {@link withNgxMetaTitleFormatter}
*
* @param features - Features to configure
*
* @public
*/
const provideNgxMetaCore = (...features) => makeEnvironmentProviders([providersFromCoreFeatures(features)]);
/**
* Provides `ngx-meta`'s core library services.
*
* Check out {@link provideNgxMetaCore} for the standalone, recommended API.
*
* Use {@link NgxMetaCoreModule.(forRoot:1)} method. Importing the module class alone does nothing.
*
* @public
*/
class NgxMetaCoreModule {
// noinspection JSDeprecatedSymbols
static forRoot(optionsOrFeature = {}, ...features) {
const optionFeaturesOrFirstFeature = isCoreFeature(optionsOrFeature)
? [optionsOrFeature]
: optionsOrFeature.defaults
? [withNgxMetaDefaults(optionsOrFeature.defaults)]
: [];
return {
ngModule: NgxMetaCoreModule,
providers: [
provideNgxMetaCore(...optionFeaturesOrFirstFeature, ...features),
],
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaCoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaCoreModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaCoreModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaCoreModule, decorators: [{
type: NgModule
}] });
/**
* @internal
*/
const _routeMetadataStrategy = () => _makeInjectionToken(ngDevMode ? 'Route metadata strategy' : 'RMS');
const injectRouteMetadataStrategy = () => inject(_routeMetadataStrategy(), { optional: true }) ?? (() => undefined);
// https://stackoverflow.com/a/8511350/3263250
const isObject = (object) => object !== null && typeof object === 'object' && !Array.isArray(object);
const metadataJsonResolver = () => _makeInjectionToken(ngDevMode ? 'JSON Resolver' : 'JR', () => (values, resolverOptions) => {
if (values === undefined) {
return;
}
const keys = [...resolverOptions.jsonPath];
let value = values;
for (const key of keys) {
if (!_isDefined(value)) {
break;
}
value = value[key];
}
const globalValue = resolverOptions.global !== undefined
? values[resolverOptions.global]
: undefined;
if (isObject(value) &&
isObject(globalValue) &&
resolverOptions.objectMerge) {
return {
...globalValue,
...value,
};
}
if (value !== undefined) {
return value;
}
return globalValue;
});
const metadataResolver = () => _makeInjectionToken(ngDevMode ? 'Metadata Resolver' : 'MRes', () => {
const jsonResolver = inject(metadataJsonResolver());
const routeMetadataStrategy = injectRouteMetadataStrategy();
const defaults = injectDefaults();
return (values, resolverOptions) => {
const value = jsonResolver(values, resolverOptions);
const routeValue = jsonResolver(routeMetadataStrategy(), resolverOptions);
const defaultValue = jsonResolver(defaults ?? undefined, resolverOptions);
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (isObject(value) &&
(isObject(routeValue) || isObject(defaultValue)) &&
resolverOptions.objectMerge) {
return {
...defaultValue,
...routeValue,
...value,
};
}
return [value, routeValue, defaultValue].find((v) => v !== undefined);
};
});
/**
* Manages the metadata values of the current page.
*
* @public
*/
class NgxMetaService {
constructor(registry, resolver) {
this.registry = registry;
this.resolver = resolver;
}
/**
* Sets the metadata values of the current page
*
* @remarks
*
* The method is designed as an atomic operation. Subsequent calls to this
* method won't set more metadata, but will instead set the metadata values
* provided when calling it.
*
* For instance,
*
* ```typescript
* ngxMetaService.set({description: 'Description'})
* ngxMetaService.set({title: 'Title'})
* ```
*
* Will result in a page with title <b>but no description</b>
*
* For more information check the {@link https://ngx-meta.dev/guides/set-metadata-using-service/ | service guide docs}
*
* @param values - Metadata values to set, as a JSON object
*/
set(values = {}) {
const allMetadata = this.registry.getAll();
for (const metadata of allMetadata) {
metadata.set(this.resolver(values, metadata.resolverOptions));
}
}
/**
* Sets a metadata value for the page
*
* You can specify which metadata elements will be changed by using the
* global or JSON Path that you would use if using {@link NgxMetaService.set} API
*
* @remarks
* For instance, if you want to just set the title of the page. You'd set it
* with {@link NgxMetaService.set} API like this:
*
* ```typescript
* this.ngxMetaService.set({
* title: 'Global title'
* standard: {
* title: 'Standard title',
* }
* })
* ```
*
* But rest of metadata would be removed.
*
* To only set the `title`, you can use this API:
*
* ```typescript
* this.ngxMetaService.setOne('title', 'Global title')
* this.ngxMetaService.setOne('standard.title', 'Standard title')
* ```
*
* For more information check the {@link https://ngx-meta.dev/guides/set-metadata-using-service/ | service guide docs}
*
* @param globalOrJsonPath - Looks for metadata managers whose global matches
* this argument. Or whose JSON path matches this
* argument.
* @param value - Value to set for matching metadata elements
*/
setOne(globalOrJsonPath, value) {
const managers = this.registry.findByGlobalOrJsonPath(globalOrJsonPath);
/* istanbul ignore next - not unit tested hence no warning test */
if (ngDevMode && [...managers].length === 0) {
console.warn(_formatDevMessage('no metadata managers found for global or JSON Path', {
module: MODULE_NAME,
value: globalOrJsonPath,
}));
}
for (const manager of managers) {
manager.set(value);
}
}
/**
* Clears all managed metadata elements of the current page
*/
clear() {
this.set();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaService, deps: [{ token: metadataRegistry() }, { token: metadataResolver() }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgxMetaService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [metadataRegistry()]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [metadataResolver()]
}] }] });
/**
* Indicate Angular's router URL shall be used to set the page's URL as a
* metadata value.
*
* Needs {@link https://ngx-meta.dev/guides/url-resolution/ | URL resolution}
* feature enabled for the feature to work
*
* A type alias exists to avoid using `typeof` around {@link AngularRouterUrl}
*
* @public
*/
const ANGULAR_ROUTER_URL = /* @__PURE__ */ Symbol(ngDevMode
? "NgxMeta: Use Angular's router URL as relative URL"
: 'NgxMetaARU');
const noOpUrlResolver = (url) => {
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (ngDevMode && url === ANGULAR_ROUTER_URL) {
console.warn(_formatDevMessage('In order to use Angular router URLs to form an absolute URL, relative URL resolution is needed. ' +
'Provide a base URL to enable URL resolution.', {
module: MODULE_NAME,
link: 'https://ngx-meta.dev/guides/url-resolution/',
}));
}
return !url ? url : url.toString();
};
/**
* Resolves relative URLs into absolute URLs if a base URL was provided.
* Otherwise, acts as a no-op and returns the input as is.
*
* Absolute URLs, nulls and undefined are also returned as is.
*
* @internal
*/
const _urlResolver = () => _makeInjectionToken(ngDevMode ? 'URL Resolver' : 'UR', () => noOpUrlResolver);
const provideDefaultUrlResolver = (baseUrl) => ({
provide: _urlResolver(),
useFactory: () => {
const router = inject(Router);
return (url) => {
if (!_isDefined(url)) {
return url;
}
const urlString = url.toString();
if (urlString.split('://').length > 1) {
return urlString;
}
const relativeUrl = url === ANGULAR_ROUTER_URL ? router.url : urlString;
/* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */
if (relativeUrl.length === 0) {
return baseUrl;
}
const baseUrlEndsWithSlash = baseUrl.endsWith('/');
const relativeUrlStartsWithSlash = relativeUrl.startsWith('/');
const pathToAppend = baseUrlEndsWithSlash && relativeUrlStartsWithSlash
? relativeUrl.slice(1)
: !baseUrlEndsWithSlash && !relativeUrlStartsWithSlash
? `/${relativeUrl}`
: relativeUrl;
return `${baseUrl}${pathToAppend}`;
};
},
});
/**
* Provides a base URL to enable resolving relative URLs. Including relative
* URLs provided by Angular's router.
*
* Metadata values requiring absolute URLs may accept relative URLs then.
* Internally, the library will turn the relative URL into an absolute URL
* using the base URL as prefix.
*
* The special value {@link ANGULAR_ROUTER_URL} can be used to query the
* Angular's router URL to be used as relative URL. Which with the feature
* enabled will be resolved into an absolute URL. Do not use the value if the
* feature isn't enabled. Otherwise, an invalid URL may end up used as
* metadata value.
*
* @example
*
* <b>Using standalone, recommended API</b>
* ```typescript
* provideNgxMetaCore(
* withNgxMetaBaseUrl('https://example.com')
* )
* ```
*
* <b>Using module-based API</b>
* ```typescript
* NgxMetaCoreModule.forRoot(
* withNgxMetaBaseUrl('https://example.com')
* )
* ```
*
* See also:
*
* - {@link provideNgxMetaCore}: to use it with the standalone, recommended API.
*
* - {@link NgxMetaCoreModule.(forRoot:1)}: to use it with the module-based API.
*
* - {@link https://ngx-meta.dev/guides/url-resolution/ | URL resolution guide}
*
*
* @param baseUrl - Prefix URL to use when relative URLs are used in metadata
* values where an absolute URL is preferred or required.
*
* @public
*/
const withNgxMetaBaseUrl = (baseUrl) => coreFeature(1 /* CoreFeatureKind.BaseUrl */, [provideDefaultUrlResolver(baseUrl)]);
/**
* Generated bundle index. Do not edit.
*/
export { ANGULAR_ROUTER_URL, NgxMetaCoreModule, NgxMetaElementsService, NgxMetaMetaService, NgxMetaMetadataLoaderModule, NgxMetaMetadataManager, NgxMetaService, _GLOBAL_APPLICATION_NAME, _GLOBAL_CANONICAL_URL, _GLOBAL_DESCRIPTION, _GLOBAL_IMAGE, _GLOBAL_LOCALE, _GLOBAL_TITLE, _composedMetadataName, _formatDevMessage, _headElementUpsertOrRemove, _injectMetadataManagers, _isDefined, _makeInjectionToken, _maybeNonHttpUrlDevMessage, _maybeTooLongDevMessage, _provideNgxMetaModuleManager, _routeMetadataStrategy, _titleFormatter, _urlResolver, _withModuleManagerNameAttribute, _withModuleManagerSameGlobalKey, _withModuleManagerSetterFactory, makeComposedKeyValMetaDefinition, makeKeyValMetaDefinition, makeMetadataManagerProviderFromSetterFactory, provideNgxMetaCore, provideNgxMetaManager, provideNgxMetaMetadataLoader, withContentAttribute, withManagerDeps, withManagerGlobal, withManagerJsonPath, withManagerObjectMerging, withNameAttribute, withNgxMetaBaseUrl, withNgxMetaDefaults, withNgxMetaTitleFormatter, withOptions, withPropertyAttribute };
//# sourceMappingURL=davidlj95-ngx-meta-core.mjs.map