How to Use 🧨 Dynamic Titles from NgRx Selectors
Tags: angular, ngrx
Published: 2022-07-16 23:33:03

When Angular 14 released and [custom title strategies became a possibility](https://dev.to/brandontroberts/setting-page-titles-natively-with-the-angular-router-393j), my mind immediately went to asking, "how can I dynamically set a page's title from a selector?"

I wrote [an RFC in the NgRx community](https://github.com/ngrx/platform/discussions/3494) to pitch an API for doing this. I'd like to explain how it works, and how you can leverage it today (whether or not it joins an @ngrx/* package).

## Example of configuring a dynamic title

All the code for the following example can be found in this [StackBlitz demo](https://stackblitz.com/edit/angular-ivy-pbyvne?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fapp-routing.module.ts,src%2Fapp%2Fapp.module.ts,src%2Fapp%2Fcounter.ts,src%2Fapp%2Ftitle-strategy.ts).

Say I have a website where I can enter the name of an "action" in an input, and then "do" it by clicking a "Do Action" button.

![Blank field titled "Action" to the left of a button titled "Do Action"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfkhyd9u6t05kev0j2lt.png)

When I click "Do Action", the title of my page reflects how many seconds have passed since the "action" was done.

![Webpage tab above the form says "6 Seconds Since Accident"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3sn5wzkkdm7xepuy0bzh.png)

In my Angular app routes, I have the route for the page configured using a tag function called `ngrxTitle` that allows me to inline selectors into a string.

```ts
const routes: Routes = [
  {
    path: '',
    component: AppComponent,
    title: ngrxTitle`${counterFeature.selectCount} Seconds Since ${counterFeature.selectEvent}`,
  },
];
```

The selector `counterFeature.selectCount` selects the number of seconds since the button was clicked, while `counterFeature.selectEvent` selects the name of the action entered in the input when the button was clicked. Using `ngrxTitle`, I can templatize the title to include the latest results of multiple selectors like these.

## ngrxTitle Implementation

`ngrxTitle` is a [tag function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) that processes a template literal with selectors.

For every selector, it generates a unique ID and replaces the selector with the string `'NgRxTitleSelector${ID}'`.

For example, when I ran my app, the title template literal was generated into the string `'NgRxTitleSelector${f35ace1e-28d8-4dc6-850a-f0900315ca8a} Seconds Since NgRxTitleSelector${40b2582b-832a-44f5-b6ce-f650518db278}'`.

Angular 14 allows developers to implement custom "title strategies". A `TitleStrategy` is a class with an `updateTitle` method that is called each time the route changes. This gives us the opportunity to change the title any way desired.

That means we can process the title template generated by `ngrxTitle` and subscribe the selectors referenced by the template to produce a new title.

The `NgRxTitleStrategy` starts with this basic structure:

```ts
export class NgRxTitleStrategy extends TitleStrategy {

  private titleSubscription: Subscription | undefined;

  updateTitle(snapshot: RouterStateSnapshot): void {
    // Each time the route changes, cancel the last subscription
    this.titleSubscription?.unsubscribe();

    // Get the title using the base method
    // When using ngrxTitle, this will be the special template string
    const titleTemplate = this.buildTitle(snapshot);
    // Create an Observable of the title built from the template
    const title$ = this.selectTitleFromTemplate(titleTemplate);
    // Continuously update the title as the selectors emit new values
    this.titleSubscription = title$.subscribe((t) => this.title.setTitle(t));
  }
}
```

In the app module, we can utilize the new title strategy in the `providers`.

```ts
@NgModule({
  declarations: [AppComponent],
  providers: [{
    provide: TitleStrategy,
    useClass: NgRxTitleStrategy,
  }],
  imports: [
    /* ... */
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
``` 

## Full implementation

See the gist below for the full implementation.

{% embed https://gist.github.com/david-shortman/660b6845054b60c42e1d3d375c043758 %}