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:
- Angular Signals: For reactive state management
- Angular Effects: For tracking changes and updating the DOM
- 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:
1. Function References (Recommended)
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() }
]
})
}
}
2. Direct Signal Access (Not Recommended)
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:
- In SSR, reactive values are resolved once when the page is rendered
- Changes to signals after initial render won't affect the server output
- 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