Angular
Core Concepts

Reactivity in Angular

Introduction

When using Unhead with Angular, you get seamless integration with Angular's reactivity system. This guide explains how reactivity works in the Angular implementation and provides best practices for managing reactive head content.

How Reactivity Works

Unhead's Angular integration leverages Angular's built-in reactivity features:

  1. Angular Signals: For reactive state management
  2. Angular Effects: For tracking changes and updating the DOM
  3. Component Lifecycle: For proper cleanup and disposal

Basic Usage

The simplest way to use reactivity is with Angular's signals:

import { Component, signal } from '@angular/core'
import { useHead } from '@unhead/angular'

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="incrementCounter()">Count: {{ counter() }}</button>
  `
})
export class CounterComponent {
  counter = signal(0)

  constructor() {
    useHead({
      title: () => `Counter: ${this.counter()}`
    })
  }

  incrementCounter() {
    this.counter.update(value => value + 1)
  }
}

In this example, every time the counter updates, the page title will automatically update to reflect the new value.

Updating Head Content After Initialization

If you need to update head content after initialization, store the reference returned by useHead():

import { Component, signal } from '@angular/core'
import { useHead } from '@unhead/angular'

@Component({
  // ...
})
export class MyComponent {
  pageTitle = signal('Initial Title')
  head = useHead({
    title: () => this.pageTitle()
  })

  updateTitle(newTitle: string) {
    this.pageTitle.set(newTitle)
  }

  // For more complex updates, use patch()
  updateHeadContent() {
    this.head.patch({
      meta: [
        { name: 'description', content: 'New description' }
      ]
    })
  }
}

Reactivity Methods

Unhead with Angular provides several ways to implement reactivity:

import { Component, signal } from '@angular/core'
import { useHead } from '@unhead/angular'

@Component({
  // ...
})
export class MyComponent {
  description = signal('Page description')

  constructor() {
    useHead({
      meta: [
        { name: 'description', content: () => this.description() }
      ]
    })
  }
}
import { Component, effect, signal } from '@angular/core'
import { useHead } from '@unhead/angular'

@Component({
  // ...
})
export class MyComponent {
  title = signal('Page Title')
  head = useHead()

  constructor() {
    // Not recommended pattern
    effect(() => {
      this.head.patch({
        title: this.title()
      })
    })
  }
}

This approach is less efficient as it creates additional effects and can lead to performance issues.

Server-Side Rendering (SSR)

When using SSR with Angular, Unhead works differently:

  1. In SSR, reactive values are resolved once when the page is rendered
  2. Changes to signals after initial render won't affect the server output
  3. The client will hydrate and take over reactivity after loading

To set initial SSR values, use the provideServerHead provider:

// app.config.server.ts
import { provideServerHead } from '@unhead/angular/server'

const serverConfig = {
  providers: [
    provideServerHead({
      init: [
        {
          htmlAttrs: {
            lang: 'en',
          },
          title: 'Server Default Title',
          meta: [
            { name: 'description', content: 'Server default description' }
          ]
        }
      ]
    }),
  ]
}

Best Practices

DO:

  • Use function references with signals: () => mySignal()
  • Store the head entry for later updates: head = useHead()
  • Update reactively using patch(): head.patch({ title: 'New Title' })
  • Set default values in server configuration

DON'T:

  • Create new head entries on every change
  • Use Angular's effect() to watch and update head contents
  • Store signal values directly in head entries
  • Create nested reactive functions (performance impact)

Lifecycle Management

Unhead's Angular integration automatically handles cleanup when components are destroyed. You don't need to manually dispose of head entries.

Under the hood, Unhead uses Angular's DestroyRef to register cleanup functions that remove head entries when components are destroyed.

Full Example

Here's a complete example showing how to implement reactivity in a component:

import { Component, computed, signal } from '@angular/core'
import { useHead, useSeoMeta } from '@unhead/angular'

@Component({
  selector: 'app-page',
  template: `
    <h1>{{ pageTitle() }}</h1>
    <input [ngModel]="pageTitle()" (ngModelChange)="setPageTitle($event)" />
    <button (click)="toggleDarkMode()">
      Toggle {{ isDarkMode() ? 'Light' : 'Dark' }} Mode
    </button>
  `
})
export class PageComponent {
  pageTitle = signal('My Reactive Page')
  isDarkMode = signal(false)

  // Computed values work well with reactivity
  bodyClass = computed(() => this.isDarkMode() ? 'dark-theme' : 'light-theme')

  // Store the head entry for later updates
  head = useHead({
    title: () => this.pageTitle(),
    bodyAttrs: {
      class: () => this.bodyClass()
    }
  })

  // Also using useSeoMeta for SEO-specific tags
  constructor() {
    useSeoMeta({
      description: () => `${this.pageTitle()} - Learn more about it!`
    })
  }

  setPageTitle(newTitle: string) {
    this.pageTitle.set(newTitle)
  }

  toggleDarkMode() {
    this.isDarkMode.update(current => !current)
  }
}

This component demonstrates:

  • Reactive title based on a signal
  • Computed body classes based on a dark mode state
  • Two-way input binding with reactive updates
  • SEO metadata that reacts to title changes
Did this page help you?