---
title: "Reactivity in Angular"
description: "Use Angular signals with useHead() for reactive head tags. Track signals in effect() and update the entry with patch()."
canonical_url: "https://unhead.unjs.io/docs/angular/head/guides/core-concepts/reactivity"
last_updated: "2026-07-29T15:04:15.119Z"
---

Create one entry with `useHead()`, read signals inside an Angular `effect()`, and patch that entry when they change. Use `useUnhead()` only when you need the underlying instance.

## Angular Integration

The Angular adapter does not subscribe to signal getters inside a head object. [Angular effects track signals read during execution](https://angular.dev/guide/signals/effect), so read each signal in an effect and pass its current value to `patch()`. The adapter ties the resulting entry to the component lifecycle.

### Track Signals in an Effect

Create the entry once and patch it from an effect:

```ts
import { Component, effect, 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)
  head = useHead()

  constructor() {
    effect(() => {
      this.head.patch({
        title: `Counter: ${this.counter()}`
      })
    })
  }

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

The effect tracks `counter()` and patches the same head entry whenever the signal changes.

### Patch an Existing Entry

Store the reference returned by `useHead()` when other methods also need to patch the entry:

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

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

  constructor() {
    effect(() => {
      this.head.patch({ title: this.pageTitle() })
    })
  }

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

  // Imperative updates can patch the same entry.
  updateHeadContent() {
    this.head.patch({
      meta: [
        { name: 'description', content: 'New description' }
      ]
    })
  }
}
```

## Reactivity Patterns

Choose between an effect and an imperative patch based on where the state changes.

### 1. Track Signals in an Effect (Recommended)

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

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

  constructor() {
    effect(() => {
      this.head.patch({
        meta: [
          { name: 'description', content: this.description() }
        ]
      })
    })
  }
}
```

### 2. Patch Imperatively

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

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

  setTitle(title: string) {
    this.title.set(title)
    this.head.patch({ title })
  }
}
```

This is useful when every state change already goes through one method. Prefer an effect when several code paths can update the signal.

## Server-Side Rendering

SSR resolves the entry for one request:

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:

```ts
// 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' }
          ]
        }
      ]
    }),
  ]
}
```

## Practical Guidelines

### DO

- Create the head entry once: `head = useHead()`
- Read signals inside `effect()` and update the entry with `patch()`
- Patch imperatively when an existing method owns the state change
- Set default values in server configuration

### DON'T

- Call `useHead()` inside an effect, which creates a new entry on each run
- Expect getter functions in a head entry to subscribe to Angular signals
- Forget to read the signal inside the effect that patches the entry

## Cleanup

The adapter registers entry disposal with Angular's [`DestroyRef`](https://angular.dev/api/core/DestroyRef), whose callbacks run when the owning component, directive, or injector is destroyed.

## Full Example

This example patches head attributes and SEO metadata from the same effect:

```ts
import { Component, computed, effect, signal } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { useHead, useSeoMeta } from '@unhead/angular'

@Component({
  selector: 'app-page',
  imports: [FormsModule],
  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')

  head = useHead()
  seo = useSeoMeta()

  constructor() {
    effect(() => {
      this.head.patch({
        title: this.pageTitle(),
        bodyAttrs: {
          class: this.bodyClass()
        }
      })
      this.seo.patch({
        description: `${this.pageTitle()} - Learn more about it!`
      })
    })
  }

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

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