Skip to content

Manage your custom metadata

In service guide, routing guide and metadata values JSON guide it is explained how to set common metadata elements provided by library's built-in metadata modules.

But if none of those fit your needs, thanks to the plug-in based architecture of the library, you can implement your own.

For instance, let's say you want to manage a <meta name='custom:title'> metadata element. And that it will be provided as the custom.title value in the metadata values JSON. For instance:

const metadataValues = {
  custom: {
    title: 'Custom title value',
  },
}

1. Implement a metadata manager

First, you'll need to implement an NgxMetaMetadataManager. A metadata manager is an abstraction whose purpose is to:

  • Set some metadata elements in the page by providing a metadata setter function
  • Specify what part of the metadata values JSON is needed to be passed to the setter function (resolution options)

This may sound frightening, but it's not!

Using a class

You can implement it by defining a class that implements the interface:

import { 
    makeKeyValMetaDefinition, 
    NgxMetaMetadataManager, 
    NgxMetaMetaService 
} from '@davidlj95/ngx-meta/core'

const JSON_PATH = ['custom', 'title']

@Injectable()
class CustomTitleMetadataManager implements NgxMetaMetadataManager<string | undefined> {
  // Convention is to name id as the JSON path to access the value from
  // the metadata values JSON
  public readonly id = JSON_PATH.join('.')

  public readonly resolverOptions: MetadataResolverOptions = {
    jsonPath: JSON_PATH,
    // 👇 If we want that global `title` key in the metadata values
    //    JSON is used as custom title if non specific is provided
    //    You can skip this one if N/A
    global: 'title' satisfies keyof GlobalMetadata,
  }

  constructor(private readonly ngxMetaMetaService: NgxMetaMetaService) {}

  // Type is constrained by specifying `<string | undefined>` above
  public set(value: string | undefined): void {
    this.ngxMetaMetaService.set(makeKeyValMetaDefinition('custom:title'), value)
  }
}

Notice the use of NgxMetaMetaService. It's a wrapper over Angular's Meta service to manage <meta> elements. With the difference that it is able to remove them from the page when value is undefined or null. For more information about how to use it, see its API reference docs

Remove the element if value is undefined (or null)

That's a convention of the library to work as expected. If you don't provide a value in your metadata values JSON, it is expected that the metadata element will disappear from the page. But as a metadata manager, you actually need to remove it! That's why NgxMetaMetaService is used, to facilitate this process.

This option is presented first as it's the traditional, Angular'ish way of working (services & @Injectable decorators). However, do check out another way of implementing it (see in below box why)

Prefer factory functions instead

Angular's @Injectable takes around 200 bytes of overhead to perform the dependency injection. If writing many managers, do prefer using providers instead (see next section)

If you don't need any dependencies injection, this approach is definitely cleaner. But that's extremely rare. You'll probably need at least the DOCUMENT to safely manage the DOM / HTML of the page.

Another way is to directly create an object implementing the interface and define an Angular Provider to inject dependencies. Specifically, a factory provider

Main benefit of this approach is bundle size reduction (see warning in previous section). Which is noticeable when writing many small managers. Which the library does and encourages you to do so.

Let's get hands-on! To avoid writing a provider yourself, the library provides a useful function to create a factory provider: makeMetadataManagerProviderFromSetterFactory. Again, don't let the scarily long name frighten you, it doesn't bite.

It takes as argument function that creates a metadata setter given some dependencies. Call it a setter factory. Then, allows you to customize the other elements of a metadata manager.

import { 
    makeKeyValMetaDefinition, 
    makeMetadataManagerProviderFromSetterFactory, 
    NgxMetaMetaService 
} from '@davidlj95/ngx-meta/core'

const CUSTOM_TITLE_METADATA_MANAGER_PROVIDER = makeMetadataManagerProviderFromSetterFactory(
  (ngxMetaMetaService: NgxMetaMetaService) => 
    ngxMetaMetaService.set(
      makeKeyValMetaDefinition('custom:title'), 
      value
    ), 
  {
    // Dependencies to pass to the setter factory
    d: [NgxMetaMetaService],

    // JSON Path to resolve the value from the values JSON
    // Will also be used as id
    jP: ['custom', 'title'],

    // 👇 If we want that global `title` key in the metadata values
    //    JSON is used as custom title if non specific is provided
    //    You can skip this one if N/A
    g: 'title' satisfies keyof GlobalMetadata,
  }
)

Notice the use of NgxMetaMetaService. It's a wrapper over Angular's Meta service to manage <meta> elements. With the difference that it is able to remove them from the page when value is undefined or null. For more information about how to use it, see its API reference docs

Remove the element if value is undefined (or null)

That's a convention of the library to work as expected. If you don't provide a value in your metadata values JSON, it is expected that the metadata element will disappear from the page. But as a metadata manager, you actually need to remove it! That's why NgxMetaMetaService is used, to facilitate this process.

That would be it, there you have your metadata manager provider, ready to inject into your Angular's app dependencies.

See the API reference of makeMetadataManagerProviderFromSetterFactory for more information

You can also check a full example at example standalone app's provideCustomMetadataManager

2. Inject it

Now that you have implemented your manager, you can inject it into your Angular's app dependencies so the library can use it.

Injecting the class

Create a class provider and add it to your main app file. In a similar fashion as you would do with a built-in metadata module like standard module in the get started setup step

Standalone, module-free apps

This is the default approach for authoring Angular projects generated with Angular CLI v17 and above. It's also the recommended way to author Angular projects right now. Using standalone APIs is the preferred and recommended way to do so. You can use standalone APIs too despite the application is still module-based. Learn more about standalone at standalone components guide and standalone migration

Open your app.config.ts file. Add the following provider:

app.config.ts
import {NgxMetaMetadataManager} from '@davidlj95/ngx-meta/core'

export const appConfig: ApplicationConfig = {
  // ...
  providers: [
    {
      provide: NgxMetaMetadataManager,
      useClass: CustomTitleMetadataManager,
      multi: true,
    }
  ]
})
Non-standalone, module-based apps

This is the default and traditional approach for authoring Angular projects generated with Angular CLI before v17. It's not the preferred or recommended way to author Angular projects right now. Using standalone APIs is the preferred and recommended way to do so. You can use standalone APIs too despite the application is still module-based. Learn more about standalone at standalone components guide and standalone migration. You can anyway keep using the module-based equivalent APIs for now if you prefer to do so.

Open your app.module.ts file. Add the following provider to the module.

app.module.ts
import {NgxMetaMetadataManager} from '@davidlj95/ngx-meta/core'

@NgModule({
  // ...
  providers: [
    {
      provide: NgxMetaMetadataManager,
      useClass: CustomTitleMetadataManager,
      multi: true,
    }
  ]
})
export class AppModule {}

Injecting the factory provider

The provider has been created already, so you just need to add it to your main app file. In a similar fashion as you would do with a built-in metadata module like standard module in the get started setup step

Standalone, module-free apps

This is the default approach for authoring Angular projects generated with Angular CLI v17 and above. It's also the recommended way to author Angular projects right now. Using standalone APIs is the preferred and recommended way to do so. You can use standalone APIs too despite the application is still module-based. Learn more about standalone at standalone components guide and standalone migration

Open your app.config.ts file. Add the created factory provider to the providers list.

app.config.ts
export const appConfig: ApplicationConfig = {
  // ...
  providers: [
    CUSTOM_TITLE_METADATA_MANAGER_PROVIDER,
  ]
})
Non-standalone, module-based apps

This is the default and traditional approach for authoring Angular projects generated with Angular CLI before v17. It's not the preferred or recommended way to author Angular projects right now. Using standalone APIs is the preferred and recommended way to do so. You can use standalone APIs too despite the application is still module-based. Learn more about standalone at standalone components guide and standalone migration. You can anyway keep using the module-based equivalent APIs for now if you prefer to do so.

Open your app.module.ts file. Add the created factory provider to the module.

app.module.ts
@NgModule({
  // ...
  providers: [
    CUSTOM_TITLE_METADATA_MANAGER_PROVIDER,
  ]
})
export class AppModule {}

You can also (lazy) load it later

You can also load the custom metadata manager later. This means it can be lazy loaded too (if you want). You can do it in a similar way you would with a built-in metadata module. Check out the late loading modules guide for more information. The only change is instead of adding a built-in metadata module, you'll add your class provider or factory provider as explained above.

❤️ Contributions are welcome!

Feel free to share your custom metadata managers with the community by making them built-in modules of the library. Built-in modules of the library are actually a bunch of grouped factory providers. The only requirements for new built-in modules to be accepted are that metadata managed:

  • Has some docs online
  • Is popular enough (a bit subjective, I know :P)

Otherwise, you can also package them and ship them separately.